1
0
mirror of https://github.com/ikatyang/emoji-cheat-sheet.git synced 2026-02-05 22:55:18 +01:00

build: update infra (#652)

This commit is contained in:
Ika
2023-07-22 19:19:36 +08:00
committed by GitHub
parent ef5ce591cb
commit 4896810dcc
23 changed files with 3158 additions and 6086 deletions

File diff suppressed because one or more lines are too long

View File

@@ -1,196 +0,0 @@
const request = require("request");
/**
* @typedef {string} EmojiLiteral
* @returns {Promise<{ [githubEmojiId: string]: EmojiLiteral | [string] }>}
*/
async function getGithubEmojiIdMap() {
return Object.fromEntries(
Object.entries(
/** @type {{ [id: string]: string }} */ (await fetchJson(
"https://api.github.com/emojis",
{
headers: {
"User-Agent": "https://github.com/ikatyang/emoji-cheat-sheet"
}
}
))
).map(([id, url]) => [
id,
url.includes("/unicode/")
? getLast(url.split("/"))
.split(".png")[0]
.split("-")
.map(codePointText =>
String.fromCodePoint(Number.parseInt(codePointText, 16))
)
.join("")
: [getLast(url.split("/")).split(".png")[0]] // github's custom emoji
])
);
}
async function getUnicodeEmojiCategoryIterator() {
return getUnicodeEmojiCategoryIteratorFromText(
await fetch("https://unicode.org/emoji/charts/full-emoji-list.txt")
);
}
/**
* @param {string} text
*/
function* getUnicodeEmojiCategoryIteratorFromText(text) {
const lines = text.split("\n");
for (const line of lines) {
if (line.startsWith("@@")) {
const value = line.substring(2);
yield { type: "category", value };
} else if (line.startsWith("@")) {
const value = line.substring(1);
yield { type: "subcategory", value };
} else if (line.length) {
const value = line
.split("\t")[0]
.split(" ")
.map(_ => String.fromCodePoint(parseInt(_, 16)))
.join("");
yield { type: "emoji", value };
}
}
}
async function getCategorizeGithubEmojiIds() {
const githubEmojiIdMap = await getGithubEmojiIdMap();
/** @type {{ [emojiLiteral: string]: string[] }} */
const emojiLiteralToGithubEmojiIdsMap = {};
/** @type {{ [githubSpecificEmojiUri: string]: string[] }} */
const githubSpecificEmojiUriToGithubEmojiIdsMap = {};
for (const [emojiId, emojiLiteral] of Object.entries(githubEmojiIdMap)) {
if (Array.isArray(emojiLiteral)) {
const [uri] = emojiLiteral;
if (!githubSpecificEmojiUriToGithubEmojiIdsMap[uri]) {
githubSpecificEmojiUriToGithubEmojiIdsMap[uri] = [];
}
githubSpecificEmojiUriToGithubEmojiIdsMap[uri].push(emojiId);
delete githubEmojiIdMap[emojiId];
continue;
}
if (!emojiLiteralToGithubEmojiIdsMap[emojiLiteral]) {
emojiLiteralToGithubEmojiIdsMap[emojiLiteral] = [];
}
emojiLiteralToGithubEmojiIdsMap[emojiLiteral].push(emojiId);
}
/** @type {{ [category: string]: { [subcategory: string]: Array<string[]> } }} */
const categorizedEmojiIds = {};
const categoryStack = [];
for (const { type, value } of await getUnicodeEmojiCategoryIterator()) {
switch (type) {
case "category": {
while (categoryStack.length) categoryStack.pop();
const title = toTitleCase(value);
categoryStack.push(title);
categorizedEmojiIds[title] = {};
break;
}
case "subcategory": {
if (categoryStack.length > 1) categoryStack.pop();
const title = toTitleCase(value);
categoryStack.push(title);
categorizedEmojiIds[categoryStack[0]][title] = [];
break;
}
case "emoji": {
const key = value.replace(/[\ufe00-\ufe0f\u200d]/g, "");
if (key in emojiLiteralToGithubEmojiIdsMap) {
const githubEmojiIds = emojiLiteralToGithubEmojiIdsMap[key];
const [category, subcategory] = categoryStack;
categorizedEmojiIds[category][subcategory].push(githubEmojiIds);
for (const githubEmojiId of githubEmojiIds) {
delete githubEmojiIdMap[githubEmojiId];
}
}
break;
}
default:
throw new Error(`Unexpected type ${JSON.stringify(type)}`);
}
}
if (Object.keys(githubEmojiIdMap).length) {
throw new Error(`Uncategorized emoji(s) found.`);
}
for (const category of Object.keys(categorizedEmojiIds)) {
const subCategorizedEmojiIds = categorizedEmojiIds[category];
const subcategories = Object.keys(subCategorizedEmojiIds);
for (const subcategory of subcategories) {
if (subCategorizedEmojiIds[subcategory].length === 0) {
delete subCategorizedEmojiIds[subcategory];
}
}
if (Object.keys(subCategorizedEmojiIds).length === 0) {
delete categorizedEmojiIds[category];
}
}
if (Object.keys(githubSpecificEmojiUriToGithubEmojiIdsMap).length) {
categorizedEmojiIds["GitHub Custom Emoji"] = {
"": Object.entries(githubSpecificEmojiUriToGithubEmojiIdsMap).map(
([, v]) => v
)
};
}
return categorizedEmojiIds;
}
/**
* @param {string} str
*/
function toTitleCase(str) {
return str
.replace(/-/g, " ")
.replace(/\s+/g, " ")
.replace(/[a-zA-Z]+/g, word => word[0].toUpperCase() + word.slice(1));
}
/**
* @template T
* @param {Array<T>} array
*/
function getLast(array) {
return array[array.length - 1];
}
/**
* @param {string} url
* @param {Partial<request.Options>} options
* @returns {Promise<any>}
*/
async function fetchJson(url, options = {}) {
return JSON.parse(await fetch(url, options));
}
/**
* @param {string} url
* @param {Partial<request.Options>} options
* @returns {Promise<string>}
*/
async function fetch(url, options = {}) {
return new Promise((resolve, reject) => {
request.get(
/** @type {request.Options} */ ({ url, ...options }),
(error, response, html) => {
if (!error && response.statusCode === 200) {
resolve(html);
} else {
reject(
error
? error
: `Unexpected response status code: ${response.statusCode}`
);
}
}
);
});
}
module.exports = {
getCategorizeGithubEmojiIds
};

160
scripts/fetch.ts Normal file
View File

@@ -0,0 +1,160 @@
type EmojiLiteral = string
async function getGithubEmojiIdMap(): Promise<{
[githubEmojiId: string]: EmojiLiteral | [string]
}> {
return Object.fromEntries(
Object.entries(
await fetchJson<{ [id: string]: string }>(
'https://api.github.com/emojis',
{
headers: {
'User-Agent': 'https://github.com/ikatyang/emoji-cheat-sheet',
},
},
),
).map(([id, url]) => [
id,
url.includes('/unicode/')
? getLast(url.split('/'))
.split('.png')[0]
.split('-')
.map(codePointText =>
String.fromCodePoint(Number.parseInt(codePointText, 16)),
)
.join('')
: [getLast(url.split('/')).split('.png')[0]], // github's custom emoji
]),
)
}
async function getUnicodeEmojiCategoryIterator() {
return getUnicodeEmojiCategoryIteratorFromText(
await fetchText('https://unicode.org/emoji/charts/full-emoji-list.txt'),
)
}
function* getUnicodeEmojiCategoryIteratorFromText(text: string) {
const lines = text.split('\n')
for (const line of lines) {
if (line.startsWith('@@')) {
const value = line.substring(2)
yield { type: 'category', value }
} else if (line.startsWith('@')) {
const value = line.substring(1)
yield { type: 'subcategory', value }
} else if (line.length) {
const value = line
.split('\t')[0]
.split(' ')
.map(_ => String.fromCodePoint(parseInt(_, 16)))
.join('')
yield { type: 'emoji', value }
}
}
}
export async function getCategorizeGithubEmojiIds() {
const githubEmojiIdMap = await getGithubEmojiIdMap()
const emojiLiteralToGithubEmojiIdsMap: {
[emojiLiteral: string]: string[]
} = {}
const githubSpecificEmojiUriToGithubEmojiIdsMap: {
[githubSpecificEmojiUri: string]: string[]
} = {}
for (const [emojiId, emojiLiteral] of Object.entries(githubEmojiIdMap)) {
if (Array.isArray(emojiLiteral)) {
const [uri] = emojiLiteral
if (!githubSpecificEmojiUriToGithubEmojiIdsMap[uri]) {
githubSpecificEmojiUriToGithubEmojiIdsMap[uri] = []
}
githubSpecificEmojiUriToGithubEmojiIdsMap[uri].push(emojiId)
delete githubEmojiIdMap[emojiId]
continue
}
if (!emojiLiteralToGithubEmojiIdsMap[emojiLiteral]) {
emojiLiteralToGithubEmojiIdsMap[emojiLiteral] = []
}
emojiLiteralToGithubEmojiIdsMap[emojiLiteral].push(emojiId)
}
const categorizedEmojiIds: {
[category: string]: { [subcategory: string]: Array<string[]> }
} = {}
const categoryStack = []
for (const { type, value } of await getUnicodeEmojiCategoryIterator()) {
switch (type) {
case 'category': {
while (categoryStack.length) categoryStack.pop()
const title = toTitleCase(value)
categoryStack.push(title)
categorizedEmojiIds[title] = {}
break
}
case 'subcategory': {
if (categoryStack.length > 1) categoryStack.pop()
const title = toTitleCase(value)
categoryStack.push(title)
categorizedEmojiIds[categoryStack[0]][title] = []
break
}
case 'emoji': {
const key = value.replace(/[\ufe00-\ufe0f\u200d]/g, '')
if (key in emojiLiteralToGithubEmojiIdsMap) {
const githubEmojiIds = emojiLiteralToGithubEmojiIdsMap[key]
const [category, subcategory] = categoryStack
categorizedEmojiIds[category][subcategory].push(githubEmojiIds)
for (const githubEmojiId of githubEmojiIds) {
delete githubEmojiIdMap[githubEmojiId]
}
}
break
}
default:
throw new Error(`Unexpected type ${JSON.stringify(type)}`)
}
}
if (Object.keys(githubEmojiIdMap).length) {
throw new Error(`Uncategorized emoji(s) found.`)
}
for (const category of Object.keys(categorizedEmojiIds)) {
const subCategorizedEmojiIds = categorizedEmojiIds[category]
const subcategories = Object.keys(subCategorizedEmojiIds)
for (const subcategory of subcategories) {
if (subCategorizedEmojiIds[subcategory].length === 0) {
delete subCategorizedEmojiIds[subcategory]
}
}
if (Object.keys(subCategorizedEmojiIds).length === 0) {
delete categorizedEmojiIds[category]
}
}
if (Object.keys(githubSpecificEmojiUriToGithubEmojiIdsMap).length) {
categorizedEmojiIds['GitHub Custom Emoji'] = {
'': Object.entries(githubSpecificEmojiUriToGithubEmojiIdsMap).map(
([, v]) => v,
),
}
}
return categorizedEmojiIds
}
function toTitleCase(text: string) {
return text
.replace(/-/g, ' ')
.replace(/\s+/g, ' ')
.replace(/[a-zA-Z]+/g, word => word[0].toUpperCase() + word.slice(1))
}
function getLast<T>(array: T[]) {
return array[array.length - 1]
}
async function fetchJson<T>(url: string, init?: RequestInit) {
const response = await fetch(url, init)
return (await response.json()) as T
}
async function fetchText(url: string, init?: RequestInit) {
const response = await fetch(url, init)
return await response.text()
}

View File

@@ -1,12 +0,0 @@
const { getCategorizeGithubEmojiIds } = require("./fetch");
const { generateCheatSheet } = require("./markdown");
async function generate() {
return generateCheatSheet(await getCategorizeGithubEmojiIds());
}
if (require.main === /** @type {unknown} */ (module)) {
generate().then(cheatSheet => console.log(cheatSheet));
} else {
module.exports = generate;
}

View File

@@ -1,8 +1,10 @@
require("jest-playback").setup(__dirname);
import { expect, test } from 'vitest'
import setupPlayback from 'jest-playback'
import { generate } from './generate.js'
const generate = require("./generate");
await setupPlayback()
test("emoji-cheat-sheet", async () => {
test('emoji-cheat-sheet', async () => {
expect(await generate()).toMatchInlineSnapshot(`
"# emoji-cheat-sheet
@@ -1594,5 +1596,5 @@ test("emoji-cheat-sheet", async () => {
| [top](#github-custom-emoji) | :rage4: | \`:rage4:\` | :shipit: | \`:shipit:\` | [top](#table-of-contents) |
| [top](#github-custom-emoji) | :suspect: | \`:suspect:\` | :trollface: | \`:trollface:\` | [top](#table-of-contents) |
"
`);
});
`)
})

10
scripts/generate.ts Normal file
View File

@@ -0,0 +1,10 @@
import { getCategorizeGithubEmojiIds } from './fetch.js'
import { generateCheatSheet } from './markdown.js'
export async function generate() {
return generateCheatSheet(await getCategorizeGithubEmojiIds())
}
if (process.argv[2] === 'run') {
console.log(await generate())
}

View File

@@ -1,134 +0,0 @@
const { name: repoName, repository } = require("../package.json");
const resource1 = "[GitHub Emoji API](https://api.github.com/emojis)";
const resoruce2 =
"[Unicode Full Emoji List](https://unicode.org/emoji/charts/full-emoji-list.html)";
const columns = 2;
const tocName = "Table of Contents";
/**
* @typedef {Array<string[]>} GithubEmojiIds
*/
/**
* @param {{ [category: string]: { [subcategory: string]: GithubEmojiIds } }} categorizedGithubEmojiIds
*/
function generateCheatSheet(categorizedGithubEmojiIds) {
const lineTexts = [];
lineTexts.push(`# ${repoName}`);
lineTexts.push("");
lineTexts.push(
`[![Up to Date](https://github.com/${repository}/workflows/Up%20to%20Date/badge.svg)](https://github.com/${repository}/actions?query=workflow%3A%22Up+to+Date%22)`
);
lineTexts.push("");
lineTexts.push(
`This cheat sheet is automatically generated from ${resource1} and ${resoruce2}.`
);
lineTexts.push("");
const categories = Object.keys(categorizedGithubEmojiIds);
lineTexts.push(`## ${tocName}`);
lineTexts.push("");
lineTexts.push(...generateToc(categories));
lineTexts.push("");
for (const category of categories) {
lineTexts.push(`### ${category}`);
lineTexts.push("");
const subcategorizeGithubEmojiIds = categorizedGithubEmojiIds[category];
const subcategories = Object.keys(subcategorizeGithubEmojiIds);
if (subcategories.length > 1) {
lineTexts.push(...generateToc(subcategories));
lineTexts.push("");
}
for (const subcategory of subcategories) {
if (subcategory) {
lineTexts.push(`#### ${subcategory}`);
lineTexts.push("");
}
lineTexts.push(
...generateTable(
subcategorizeGithubEmojiIds[subcategory],
`[top](#${getHeaderId(category)})`,
`[top](#${getHeaderId(tocName)})`
)
);
lineTexts.push("");
}
}
return lineTexts.join("\n");
}
/**
* @param {string[]} headers
*/
function generateToc(headers) {
return headers.map(header => `- [${header}](#${getHeaderId(header)})`);
}
/**
* @param {string} header
*/
function getHeaderId(header) {
return header
.toLowerCase()
.replace(/ /g, "-")
.replace(/[^a-z0-9-]/g, "");
}
/**
* @param {GithubEmojiIds} githubEmojiIds
* @param {string} leftText
* @param {string} rightText
*/
function generateTable(githubEmojiIds, leftText, rightText) {
const lineTexts = [];
let header = "";
let delimieter = "";
header += "| ";
delimieter += "| - ";
for (let i = 0; i < columns && i < githubEmojiIds.length; i++) {
header += `| ico | shortcode `;
delimieter += "| :-: | - ";
}
header += "| |";
delimieter += "| - |";
lineTexts.push(header, delimieter);
for (let i = 0; i < githubEmojiIds.length; i += columns) {
let lineText = `| ${leftText} `;
for (let j = 0; j < columns; j++) {
if (i + j < githubEmojiIds.length) {
const emojiIds = githubEmojiIds[i + j];
const emojiId = emojiIds[0];
lineText += `| :${emojiId}: | \`:${emojiId}:\` `;
for (let k = 1; k < emojiIds.length; k++) {
lineText += `<br /> \`:${emojiIds[k]}:\` `;
}
} else if (githubEmojiIds.length > columns) {
lineText += "| | ";
}
}
lineText += `| ${rightText} |`;
lineTexts.push(lineText);
}
return lineTexts;
}
module.exports = {
generateCheatSheet
};

120
scripts/markdown.ts Normal file
View File

@@ -0,0 +1,120 @@
import { name as repoName, repository } from '../package.json'
const RESOURCE_1 = '[GitHub Emoji API](https://api.github.com/emojis)'
const RESOURCE_2 =
'[Unicode Full Emoji List](https://unicode.org/emoji/charts/full-emoji-list.html)'
const COLUMNS = 2
const TOC_NAME = 'Table of Contents'
type GithubEmojiIds = Array<string[]>
export function generateCheatSheet(categorizedGithubEmojiIds: {
[category: string]: { [subCategory: string]: GithubEmojiIds }
}) {
const lineTexts = []
lineTexts.push(`# ${repoName}`)
lineTexts.push('')
lineTexts.push(
`[![Up to Date](https://github.com/${repository}/workflows/Up%20to%20Date/badge.svg)](https://github.com/${repository}/actions?query=workflow%3A%22Up+to+Date%22)`,
)
lineTexts.push('')
lineTexts.push(
`This cheat sheet is automatically generated from ${RESOURCE_1} and ${RESOURCE_2}.`,
)
lineTexts.push('')
const categories = Object.keys(categorizedGithubEmojiIds)
lineTexts.push(`## ${TOC_NAME}`)
lineTexts.push('')
lineTexts.push(...generateToc(categories))
lineTexts.push('')
for (const category of categories) {
lineTexts.push(`### ${category}`)
lineTexts.push('')
const subCategorizedGithubEmojiIds = categorizedGithubEmojiIds[category]
const subCategories = Object.keys(subCategorizedGithubEmojiIds)
if (subCategories.length > 1) {
lineTexts.push(...generateToc(subCategories))
lineTexts.push('')
}
for (const subCategory of subCategories) {
if (subCategory) {
lineTexts.push(`#### ${subCategory}`)
lineTexts.push('')
}
lineTexts.push(
...generateTable(
subCategorizedGithubEmojiIds[subCategory],
`[top](#${getHeaderId(category)})`,
`[top](#${getHeaderId(TOC_NAME)})`,
),
)
lineTexts.push('')
}
}
return lineTexts.join('\n')
}
function generateToc(headers: string[]) {
return headers.map(header => `- [${header}](#${getHeaderId(header)})`)
}
function getHeaderId(header: string) {
return header
.toLowerCase()
.replace(/ /g, '-')
.replace(/[^a-z0-9-]/g, '')
}
function generateTable(
githubEmojiIds: GithubEmojiIds,
leftText: string,
rightText: string,
) {
const lineTexts = []
let header = ''
let delimiter = ''
header += '| '
delimiter += '| - '
for (let i = 0; i < COLUMNS && i < githubEmojiIds.length; i++) {
header += `| ico | shortcode `
delimiter += '| :-: | - '
}
header += '| |'
delimiter += '| - |'
lineTexts.push(header, delimiter)
for (let i = 0; i < githubEmojiIds.length; i += COLUMNS) {
let lineText = `| ${leftText} `
for (let j = 0; j < COLUMNS; j++) {
if (i + j < githubEmojiIds.length) {
const emojiIds = githubEmojiIds[i + j]
const emojiId = emojiIds[0]
lineText += `| :${emojiId}: | \`:${emojiId}:\` `
for (let k = 1; k < emojiIds.length; k++) {
lineText += `<br /> \`:${emojiIds[k]}:\` `
}
} else if (githubEmojiIds.length > COLUMNS) {
lineText += '| | '
}
}
lineText += `| ${rightText} |`
lineTexts.push(lineText)
}
return lineTexts
}