diff --git a/README.md b/README.md index 58ad0afab..db5e37ae1 100644 --- a/README.md +++ b/README.md @@ -149,7 +149,9 @@ jobs: with: USERNAME: ${{ github.repository_owner }} # UTC_OFFSET is optional, default to zero - UTC_OFFSET: 8 + UTC_OFFSET: 8 + # EXCLUDE is an optional comma seperated list of languages to exclude, defaults to "" + EXCLUDE: "" ``` --- diff --git a/action.yml b/action.yml index 0f9f9aa7e..6c26c3406 100644 --- a/action.yml +++ b/action.yml @@ -11,6 +11,10 @@ inputs: required: false description: 'The UTC offset used in the Productive Time Card.(e.g., 8, -3)' default: 0 + EXCLUDE: + required: false + description: 'A comma seperated list of languages to hide' + default: '' runs: using: 'node16' diff --git a/api/cards/most-commit-language.ts b/api/cards/most-commit-language.ts index 75f02efb2..f60233670 100644 --- a/api/cards/most-commit-language.ts +++ b/api/cards/most-commit-language.ts @@ -1,10 +1,12 @@ import {getCommitsLanguageSVGWithThemeName} from '../../src/cards/most-commit-lauguage-card'; import {changToNextGitHubToken} from '../utils/github-token-updater'; import {getErrorMsgCard} from '../utils/error-card'; +import {translateLanguage} from '../../src/utils/translator' import type {VercelRequest, VercelResponse} from '@vercel/node'; export default async (req: VercelRequest, res: VercelResponse) => { - const {username, theme = 'default'} = req.query; + let {username, theme = 'default', exclude = ""} = req.query; + if (typeof theme !== 'string') { res.status(400).send('theme must be a string'); return; @@ -13,12 +15,20 @@ export default async (req: VercelRequest, res: VercelResponse) => { res.status(400).send('username must be a string'); return; } + if (typeof exclude !== 'string') { + res.status(400).send('exclude must be a string'); + return; + } + let excludeArr = []; + exclude.split(",").forEach(function(val){ + excludeArr.push(translateLanguage(val)); + }); try { let tokenIndex = 0; while (true) { try { - const cardSVG = await getCommitsLanguageSVGWithThemeName(username, theme); + const cardSVG = await getCommitsLanguageSVGWithThemeName(username, theme, excludeArr); res.setHeader('Content-Type', 'image/svg+xml'); res.send(cardSVG); return; diff --git a/api/cards/repos-per-language.ts b/api/cards/repos-per-language.ts index 3413907f8..461bf0ee9 100644 --- a/api/cards/repos-per-language.ts +++ b/api/cards/repos-per-language.ts @@ -1,10 +1,12 @@ import {getReposPerLanguageSVGWithThemeName} from '../../src/cards/repos-per-language-card'; import {changToNextGitHubToken} from '../utils/github-token-updater'; import {getErrorMsgCard} from '../utils/error-card'; +import {translateLanguage} from '../../src/utils/translator' import type {VercelRequest, VercelResponse} from '@vercel/node'; export default async (req: VercelRequest, res: VercelResponse) => { - const {username, theme = 'default'} = req.query; + let {username, theme = 'default', exclude = ""} = req.query; + if (typeof theme !== 'string') { res.status(400).send('theme must be a string'); return; @@ -13,11 +15,20 @@ export default async (req: VercelRequest, res: VercelResponse) => { res.status(400).send('username must be a string'); return; } + if (typeof exclude !== 'string') { + res.status(400).send('exclude must be a string'); + return; + } + let excludeArr = []; + exclude.split(",").forEach(function(val){ + excludeArr.push(translateLanguage(val)); + }); + try { let tokenIndex = 0; while (true) { try { - const cardSVG = await getReposPerLanguageSVGWithThemeName(username, theme); + const cardSVG = await getReposPerLanguageSVGWithThemeName(username, theme, excludeArr); res.setHeader('Content-Type', 'image/svg+xml'); res.send(cardSVG); return; @@ -29,7 +40,6 @@ export default async (req: VercelRequest, res: VercelResponse) => { } } } catch (err: any) { - console.log(err); res.send(getErrorMsgCard(err.message, theme)); } }; diff --git a/package-lock.json b/package-lock.json index 66e3fa09a..6f5266381 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11413,4 +11413,4 @@ "dev": true } } -} +} \ No newline at end of file diff --git a/package.json b/package.json index 2556ffd3e..6aa360337 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ "js-abbreviation-number": "^1.4.0", "jsdom": "^16.4.0", "moment": "^2.29.2", - "retry-axios": "^2.6.0" + "retry-axios": "^2.6.0", }, "devDependencies": { "@types/d3": "^7.1.0", diff --git a/src/app.ts b/src/app.ts index daa390b7a..832da1d12 100644 --- a/src/app.ts +++ b/src/app.ts @@ -5,6 +5,7 @@ import {createCommitsPerLanguageCard} from './cards/most-commit-lauguage-card'; import {createStatsCard} from './cards/stats-card'; import {createProductiveTimeCard} from './cards/productive-time-card'; import {spawn} from 'child_process'; +import { translateLanguage } from './utils/translator'; import {OUTPUT_PATH, generatePreviewMarkdown} from './utils/file-writer'; const execCmd = (cmd: string, args: string[] = []) => @@ -39,6 +40,8 @@ const action = async () => { core.info(`Username: ${username}`); const utcOffset = Number(core.getInput('UTC_OFFSET', {required: false})); core.info(`UTC offset: ${utcOffset}`); + const exclude = core.getInput('EXCLUE', {required: false}).split(","); + core.info(`Excluded languages: ${utcOffset}`); try { // Remove old output core.info(`Remove old cards...`); @@ -55,7 +58,7 @@ const action = async () => { // ReposPerLanguageCard try { core.info(`Creating ReposPerLanguageCard...`); - await createReposPerLanguageCard(username); + await createReposPerLanguageCard(username, exclude); } catch (error: any) { core.error(`Error when creating ReposPerLanguageCard \n${error.stack}`); } @@ -63,7 +66,7 @@ const action = async () => { // CommitsPerLanguageCard try { core.info(`Creating CommitsPerLanguageCard...`); - await createCommitsPerLanguageCard(username); + await createCommitsPerLanguageCard(username, exclude); } catch (error: any) { core.error(`Error when creating CommitsPerLanguageCard \n${error.stack}`); } @@ -112,11 +115,11 @@ const action = async () => { } }; -const main = async (username: string, utcOffset: number) => { +const main = async (username: string, utcOffset: number, exclude: Array) => { try { await createProfileDetailsCard(username); - await createReposPerLanguageCard(username); - await createCommitsPerLanguageCard(username); + await createReposPerLanguageCard(username, exclude); + await createCommitsPerLanguageCard(username, exclude); await createStatsCard(username); await createProductiveTimeCard(username, utcOffset); generatePreviewMarkdown(false); @@ -132,5 +135,12 @@ if (process.argv.length == 2) { } else { const username = process.argv[2]; const utcOffset = Number(process.argv[3]); - main(username, utcOffset); + let exclude: Array = []; + if(process.argv[4]){ + process.argv[4].split(",").forEach(function(val){ + exclude.push(translateLanguage(val)); + }); + }; + console.log(exclude) + main(username, utcOffset, exclude); } diff --git a/src/cards/most-commit-lauguage-card.ts b/src/cards/most-commit-lauguage-card.ts index 323825969..bbd6bdc2b 100644 --- a/src/cards/most-commit-lauguage-card.ts +++ b/src/cards/most-commit-lauguage-card.ts @@ -3,8 +3,8 @@ import {getCommitLanguage, CommitLanguages} from '../github-api/commits-per-lang import {createDonutChartCard} from '../templates/donut-chart-card'; import {writeSVG} from '../utils/file-writer'; -export const createCommitsPerLanguageCard = async function (username: string) { - const statsData = await getCommitsLanguageData(username); +export const createCommitsPerLanguageCard = async function (username: string, exclude: Array) { + const statsData = await getCommitsLanguageData(username, exclude); for (const themeName of ThemeMap.keys()) { const svgString = getCommitsLanguageSVG(statsData, themeName); // output to folder, use 2- prefix for sort in preview @@ -14,10 +14,11 @@ export const createCommitsPerLanguageCard = async function (username: string) { export const getCommitsLanguageSVGWithThemeName = async function ( username: string, - themeName: string + themeName: string, + exclude: Array ): Promise { if (!ThemeMap.has(themeName)) throw new Error('Theme does not exist'); - const langData = await getCommitsLanguageData(username); + const langData = await getCommitsLanguageData(username, exclude); return getCommitsLanguageSVG(langData, themeName); }; @@ -47,9 +48,10 @@ const getCommitsLanguageSVG = function ( }; const getCommitsLanguageData = async function ( - username: string + username: string, + exclude: Array ): Promise<{name: string; value: number; color: string}[]> { - const commitLanguages: CommitLanguages = await getCommitLanguage(username); + const commitLanguages: CommitLanguages = await getCommitLanguage(username, exclude); let langData = []; // make a pie data diff --git a/src/cards/repos-per-language-card.ts b/src/cards/repos-per-language-card.ts index 1233f74c9..5350006ec 100644 --- a/src/cards/repos-per-language-card.ts +++ b/src/cards/repos-per-language-card.ts @@ -3,8 +3,8 @@ import {getRepoLanguages} from '../github-api/repos-per-language'; import {createDonutChartCard} from '../templates/donut-chart-card'; import {writeSVG} from '../utils/file-writer'; -export const createReposPerLanguageCard = async function (username: string) { - const langData = await getRepoLanguageData(username); +export const createReposPerLanguageCard = async function (username: string, exclude: Array) { + const langData = await getRepoLanguageData(username, exclude); for (const themeName of ThemeMap.keys()) { const svgString = getReposPerLanguageSVG(langData, themeName); // output to folder, use 1- prefix for sort in preview @@ -12,9 +12,13 @@ export const createReposPerLanguageCard = async function (username: string) { } }; -export const getReposPerLanguageSVGWithThemeName = async function (username: string, themeName: string) { +export const getReposPerLanguageSVGWithThemeName = async function ( + username: string, + themeName: string, + exclude: Array +) { if (!ThemeMap.has(themeName)) throw new Error('Theme does not exist'); - const langData = await getRepoLanguageData(username); + const langData = await getRepoLanguageData(username, exclude); return getReposPerLanguageSVG(langData, themeName); }; @@ -23,8 +27,8 @@ const getReposPerLanguageSVG = function (langData: {name: string; value: number; return svgString; }; -const getRepoLanguageData = async function (username: string) { - const repoLanguages = await getRepoLanguages(username); +const getRepoLanguageData = async function (username: string, exclude: Array) { + const repoLanguages = await getRepoLanguages(username, exclude); let langData = []; // make a pie data diff --git a/src/github-api/commits-per-language.ts b/src/github-api/commits-per-language.ts index c8455c810..2be6522fc 100644 --- a/src/github-api/commits-per-language.ts +++ b/src/github-api/commits-per-language.ts @@ -61,7 +61,7 @@ const fetcher = (token: string, variables: any) => { }; // repos per language -export async function getCommitLanguage(username: string): Promise { +export async function getCommitLanguage(username: string, exclude: Array): Promise { const commitLanguages = new CommitLanguages(); const res = await fetcher(process.env.GITHUB_TOKEN!, { @@ -83,7 +83,9 @@ export async function getCommitLanguage(username: string): Promise { }; // repos per language -export async function getRepoLanguages(username: string): Promise { +export async function getRepoLanguages(username: string, exclude: Array): Promise { let hasNextPage = true; let cursor = null; const repoLanguages = new RepoLanguages(); @@ -85,7 +85,9 @@ export async function getRepoLanguages(username: string): Promise if (node.primaryLanguage) { const langName = node.primaryLanguage.name; const langColor = node.primaryLanguage.color; - repoLanguages.addLanguage(langName, langColor); + if (!exclude.includes(langName)) { + repoLanguages.addLanguage(langName, langColor); + } } }); diff --git a/src/utils/translator.ts b/src/utils/translator.ts new file mode 100644 index 000000000..0290879af --- /dev/null +++ b/src/utils/translator.ts @@ -0,0 +1,273 @@ +export const translateLanguage = function async(lang: string) { + // this is a list of all Github supported languages + // that have known aliases + // aliases with non URL safe characters have been removed + // https://github.com/github/linguist/blob/master/lib/linguist/languages.yml + // some keys added (cs: C#) etc. + const dict: {[id: string]: string} = { + ags: 'AGS Script', + aspx: 'ASP.NET', + ats2: 'ATS', + actionscript3: 'ActionScript', + as3: 'ActionScript', + ada95: 'Ada', + ada2005: 'Ada', + acfm: 'Adobe Font Metrics', + amfm: 'Adobe Font Metrics', + abuild: 'Alpine Abuild', + apkbuild: 'Alpine Abuild', + altium: 'Altium Designer', + aconf: 'ApacheConf', + apache: 'ApacheConf', + osascript: 'AppleScript', + asm: 'Assembly', + nasm: 'Assembly', + ahk: 'AutoHotkey', + au3: 'AutoIt', + AutoIt3: 'AutoIt', + AutoItScript: 'AutoIt', + bat: 'Batchfile', + batch: 'Batchfile', + dosbatch: 'Batchfile', + winbatch: 'Batchfile', + be: 'Berry', + b3d: 'BlitzBasic', + blitz3d: 'BlitzBasic', + blitzplus: 'BlitzBasic', + bplus: 'BlitzBasic', + bmax: 'BlitzMax', + csharp: 'C#', + cs: 'C#', + cake: 'C#', + cakescript: 'C#', + cpp: 'C++', + c2hs: 'C2hs Haskell', + cds: 'CAP CDS', + Cabal: 'Cabal Config', + Carto: 'CartoCSS', + chpl: 'Chapel', + checksum: 'Checksums', + hash: 'Checksums', + hashes: 'Checksums', + sum: 'Checksums', + sums: 'Checksums', + asp: 'Classic ASP', + soy: 'Closure Templates', + CoNLL: 'CoNLL-U', + ql: 'CodeQL', + coffee: 'CoffeeScript', + cfm: 'ColdFusion', + cfml: 'ColdFusion', + cfc: 'ColdFusion CFC', + lisp: 'Common Lisp', + cwl: 'Common Workflow Language', + pyrex: 'Cython', + Dlang: 'D', + dcl: 'DIGITAL Command Language', + byond: 'DM', + dpatch: 'Darcs Patch', + udiff: 'Diff', + Containerfile: 'Dockerfile', + email: 'E-mail', + eml: 'E-mail', + mail: 'E-mail', + mbox: 'E-mail', + Earthfile: 'Earthly', + elisp: 'Emacs Lisp', + emacs: 'Emacs Lisp', + fsharp: 'F#', + fstar: 'F*', + FIGfont: 'FIGlet Font', + fb: 'FreeBasic', + ftl: 'FreeMarker', + pot: 'Gettext Catalog', + cucumber: 'Gherkin', + gitattributes: 'Git Attributes', + gitconfig: 'Git Config', + gitmodules: 'Git Config', + golang: 'Go', + gf: 'Grammatical Framework', + gsp: 'Groovy Server Pages', + terraform: 'HCL', + xhtml: 'HTML', + ecr: 'HTML+ECR', + eex: 'HTML+EEX', + heex: 'HTML+EEX', + leex: 'HTML+EEX', + erb: 'HTML+ERB', + rhtml: 'HTML+ERB', + razor: 'HTML+Razor', + hbs: 'Handlebars', + htmlbars: 'Handlebars', + hylang: 'Hy', + igor: 'IGOR Pro', + igorpro: 'IGOR Pro', + dosini: 'INI', + irc: 'IRC log', + ignore: 'Ignore List', + gitignore: 'Ignore List', + ijm: 'ImageJ Macro', + i7: 'Inform 7', + inform7: 'Inform 7', + geojson: 'JSON', + jsonl: 'JSON', + topojson: 'JSON', + jsonc: 'JSON with Comments', + jsp: 'Java Server Pages', + js: 'JavaScript', + node: 'JavaScript', + mps: 'JetBrains MPS', + django: 'Jinja', + htmldjango: 'Jinja', + ksy: 'Kaitai Struct', + kak: 'KakouneScript', + kakscript: 'KakouneScript', + pcbnew: 'KiCad Layout', + lassoscript: 'Lasso', + flex: 'Lex', + litcoffee: 'Literate CoffeeScript', + lhaskell: 'Literate Haskell', + lhs: 'Literate Haskell', + ls: 'LiveScript', + mumps: 'M', + autoconf: 'M4Sugar', + octave: 'MATLAB', + m2: 'Macaulay2', + bsdmake: 'Makefile', + make: 'Makefile', + mf: 'Makefile', + pandoc: 'Markdown', + markojs: 'Marko', + mma: 'Mathematica', + wolfram: 'Mathematica', + wl: 'Mathematica', + maxmsp: 'Max', + m68k: 'Motorola 68K Assembly', + amusewiki: 'Muse', + npmrc: 'NPM Config', + nixos: 'Nix', + nush: 'Nu', + njk: 'Nunjucks', + objc: 'Objective-C', + objectivec: 'Objective-C', + objectivej: 'Objective-J', + objj: 'Objective-J', + odinlang: 'Odin', + progress: 'OpenEdge ABL', + openedge: 'OpenEdge ABL', + abl: 'OpenEdge ABL', + openrc: 'OpenRC runscript', + AFDKO: 'OpenType Feature File', + inc: 'PHP', + povray: 'POV-Ray SDL', + pasm: 'Parrot Assembly', + pir: 'Parrot Internal Representation', + delphi: 'Pascal', + objectpascal: 'Pascal', + cperl: 'Perl', + postscr: 'PostScript', + posh: 'PowerShell', + pwsh: 'PowerShell', + protobuf: 'Protocol Buffer', + python3: 'Python', + py: 'Python', + rusthon: 'Python', + pycon: 'Python console', + qsharp: 'Q#', + R: 'R', + Rscript: 'R', + splus: 'R', + arexx: 'REXX', + rpcgen: 'RPC', + oncrpc: 'RPC', + xdr: 'RPC', + sqlrpgle: 'RPGLE', + specfile: 'RPM Spec', + perl6: 'Raku', + raw: 'Raw token data', + inputrc: 'Readline Config', + readline: 'Readline Config', + redirects: 'Redirect Rules', + regexp: 'Regular Expression', + regex: 'Regular Expression', + renpy: "Ren'Py", + groff: 'Roff', + man: 'Roff', + manpage: 'Roff', + mdoc: 'Roff', + nroff: 'Roff', + troff: 'Roff', + jruby: 'Ruby', + macruby: 'Ruby', + rake: 'Ruby', + rb: 'Ruby', + rbx: 'Ruby', + rs: 'Rust', + sepolicy: 'SELinux Policy', + stla: 'STL', + saltstate: 'SaltStack', + salt: 'SaltStack', + sh: 'Shell', + bash: 'Shell', + zsh: 'Shell', + shellcheckrc: 'ShellCheck Config', + console: 'ShellSession', + coccinelle: 'SmPL', + squeak: 'Smalltalk', + sourcemod: 'SourcePawn', + sml: 'Standard ML', + bazel: 'Starlark', + bzl: 'Starlark', + latex: 'TeX', + fundamental: 'Text', + tl: 'Type Language', + ts: 'TypeScript', + gas: 'Unix Assembly', + Ur: 'UrWeb', + vlang: 'V', + vb6: 'VBA', + keyvalues: 'Valve Data Format', + vdf: 'Valve Data Format', + vtl: 'Velocity Template Language', + velocity: 'Velocity Template Language', + help: 'Vim Help File', + vimhelp: 'Vim Help File', + vim: 'Vim Script', + viml: 'Vim Script', + nvim: 'Vim Script', + SnipMate: 'Vim Snippet', + UltiSnip: 'Vim Snippet', + UltiSnips: 'Vim Snippet', + NeoSnippet: 'Vim Snippet', + vbnet: 'Visual Basic .NET', + wast: 'WebAssembly', + wasm: 'WebAssembly', + vtt: 'WebVTT', + wgetrc: 'Wget Config', + mediawiki: 'Wikitext', + wiki: 'Wikitext', + wrenlang: 'Wren', + xbm: 'X BitMap', + xpm: 'X PixMap', + xten: 'X10', + rss: 'XML', + xsd: 'XML', + wsdl: 'XML', + xsl: 'XSLT', + yml: 'YAML', + snippet: 'YASnippet', + yas: 'YASnippet', + bro: 'Zeek', + curlrc: 'cURL Config', + rst: 'reStructuredText', + robots: 'robots.txt', + advpl: 'xBase', + clipper: 'xBase', + html: 'HTML', + foxpro: 'xBase' + }; + if (dict[lang] !== undefined) { + return dict[lang]; + } + return lang.charAt(0).toUpperCase() + lang.slice(1); +}; diff --git a/tests/github-api/commits-per-language.test.ts b/tests/github-api/commits-per-language.test.ts index 01c104b33..4bd70ee47 100644 --- a/tests/github-api/commits-per-language.test.ts +++ b/tests/github-api/commits-per-language.test.ts @@ -73,7 +73,7 @@ afterEach(() => { describe('commit contributions on github', () => { it('should get correct commit contributions', async () => { mock.onPost('https://api.github.com/graphql').reply(200, data); - const totalContributions = await getCommitLanguage('vn7n24fzkq'); + const totalContributions = await getCommitLanguage('vn7n24fzkq', []); expect(totalContributions).toEqual({ languageMap: new Map([ ['Rust', {color: '#dea584', count: 199, name: 'Rust'}], @@ -84,6 +84,6 @@ describe('commit contributions on github', () => { it('should throw error when api failed', async () => { mock.onPost('https://api.github.com/graphql').reply(200, error); - await expect(getCommitLanguage('vn7n24fzkq')).rejects.toThrow('GitHub api failed'); + await expect(getCommitLanguage('vn7n24fzkq', [])).rejects.toThrow('GitHub api failed'); }); }); diff --git a/tests/github-api/repos-per-language.test.ts b/tests/github-api/repos-per-language.test.ts index 73b10fe11..660981582 100644 --- a/tests/github-api/repos-per-language.test.ts +++ b/tests/github-api/repos-per-language.test.ts @@ -78,7 +78,7 @@ describe('repos per language on github', () => { .onPost('https://api.github.com/graphql') .replyOnce(200, lastData) .onAny(); - const repoData = await getRepoLanguages('vn7n24fzkq'); + const repoData = await getRepoLanguages('vn7n24fzkq', []); expect(repoData).toEqual({ languageMap: new Map([ ['Java', {color: '#b07219', count: 2, name: 'Java'}], @@ -90,6 +90,6 @@ describe('repos per language on github', () => { it('should throw error when api failed', async () => { mock.onPost('https://api.github.com/graphql').reply(200, error); - await expect(getRepoLanguages('vn7n24fzkq')).rejects.toThrow('GitHub api failed'); + await expect(getRepoLanguages('vn7n24fzkq', [])).rejects.toThrow('GitHub api failed'); }); });