Commit 6eb345f

Anton Golub <antongolub@antongolub.com>
2023-10-12 22:29:11
fix: separate presenters processing, fix `compact` filter
1 parent 6a72a7f
src/main.ts
@@ -6,12 +6,10 @@ import { Octokit, RequestError } from 'octokit'
 import { retry } from '@octokit/plugin-retry'
 import { throttling } from '@octokit/plugin-throttling'
 import { collect, Data } from './collect/collect.js'
-import { allBadges } from './all-badges/index.js'
-import { Badge, badgeCollection, BadgePresenter, ID } from './badges.js'
+import { Badge } from './badges.js'
 import { updateReadme } from './update-readme.js'
 import { updateBadges } from './update-badges.js'
-import path from 'node:path'
-import { fileURLToPath } from 'node:url'
+import { presentBadges } from './present-badges.js'
 
 void (async function main() {
   const { env } = process
@@ -107,62 +105,7 @@ void (async function main() {
     }
   }
 
-  for (const { default: it } of allBadges) {
-    const presenter: BadgePresenter = it
-    const newBadges: Badge[] = []
-    const grant = badgeCollection(newBadges)
-    presenter.present(data, grant)
-
-    if (newBadges.length === 0) {
-      continue
-    }
-
-    // Enhance badges with image URLs.
-    for (const b of newBadges) {
-      const baseDir = path.basename(path.dirname(fileURLToPath(presenter.url)))
-      b.image = `https://github.com/my-badges/my-badges/blob/master/src/all-badges/${baseDir}/${b.id}.png?raw=true`
-    }
-
-    const badgeFromPresenter = (x: Badge) =>
-      (presenter.badges as ID[]).includes(x.id)
-
-    // Merge existing userBadges with newBadges.
-    if (compact && presenter.tiers) {
-      const newHighestTierBadge = newBadges.reduce((prev, curr) => {
-        return prev.tier > curr.tier ? prev : curr
-      })
-
-      const existingBadgeIndex = userBadges.findIndex(badgeFromPresenter)
-      if (existingBadgeIndex === -1) {
-        userBadges.push(newHighestTierBadge)
-      } else if (
-        newHighestTierBadge.tier >= userBadges[existingBadgeIndex].tier
-      ) {
-        userBadges[existingBadgeIndex] = newHighestTierBadge
-
-        // Drop all other badges from the same presenter.
-        userBadges = userBadges.filter(
-          (x, i) => i === existingBadgeIndex || !badgeFromPresenter(x),
-        )
-      }
-    } else {
-      for (const badge of newBadges) {
-        const index = userBadges.findIndex((x) => x.id === badge.id)
-        if (index === -1) {
-          userBadges.push(badge)
-        } else {
-          userBadges[index] = badge
-        }
-      }
-    }
-  }
-
-  if (pickBadges.length > 0) {
-    userBadges = userBadges.filter((x) => pickBadges.includes(x.id))
-  }
-  if (omitBadges.length > 0) {
-    userBadges = userBadges.filter((x) => !omitBadges.includes(x.id))
-  }
+  userBadges = presentBadges(data, userBadges, pickBadges, omitBadges, compact)
 
   console.log(JSON.stringify(userBadges, null, 2))
 
src/present-badges.ts
@@ -0,0 +1,67 @@
+import { Badge, badgeCollection, BadgePresenter, ID } from './badges.js'
+import { allBadges } from './all-badges/index.js'
+import path from 'node:path'
+import { fileURLToPath } from 'node:url'
+import { Data } from './collect/collect.js'
+
+export const mergeBadges = (...badges: (Badge | Badge[])[]): Badge[] =>
+  Object.values(
+    badges
+      .flat()
+      .reduce<Record<string, Badge>>(
+        (m, v) => Object.assign(m, { [v.id]: v }),
+        {},
+      ),
+  )
+
+export const presentBadges = (
+  data: Data,
+  userBadges: Badge[],
+  pickBadges: string[],
+  omitBadges: string[],
+  compact: boolean,
+): Badge[] => {
+  const newBadges: Badge[] = []
+  const presenters: BadgePresenter[] = allBadges.map((m) => m.default)
+  for (const presenter of presenters) {
+    const grant = badgeCollection(newBadges)
+    presenter.present(data, grant)
+
+    // Enhance badges with image URLs.
+    for (const b of newBadges) {
+      const baseDir = path.basename(path.dirname(fileURLToPath(presenter.url)))
+      b.image = `https://github.com/my-badges/my-badges/blob/master/src/all-badges/${baseDir}/${b.id}.png?raw=true`
+    }
+  }
+
+  userBadges = mergeBadges(userBadges, newBadges)
+
+  if (compact) {
+    for (const presenter of presenters) {
+      if (!presenter.tiers) {
+        continue
+      }
+      const touchedBadges = userBadges.filter(({ id }) =>
+        (presenter.badges as ID[]).includes(id),
+      )
+      const newHighestTierBadge = touchedBadges.reduce(
+        (prev, curr) => (prev.tier > curr.tier ? prev : curr),
+        {} as Badge,
+      )
+
+      omitBadges.push(
+        ...touchedBadges
+          .map(({ id }) => id)
+          .filter((id) => id !== newHighestTierBadge.id),
+      )
+    }
+  }
+  if (pickBadges.length > 0) {
+    userBadges = userBadges.filter((x) => pickBadges.includes(x.id))
+  }
+  if (omitBadges.length > 0) {
+    userBadges = userBadges.filter((x) => !omitBadges.includes(x.id))
+  }
+
+  return userBadges
+}
src/utils.ts
@@ -2,6 +2,7 @@ import fs from 'node:fs/promises'
 import path from 'node:path'
 import { Octokit } from 'octokit'
 import { Commit, Pull } from './collect/collect.js'
+import { Badge } from './badges.js'
 
 export function linkCommit(commit: Commit): string {
   return `<a href="https://github.com/${commit.repository.owner.login}/${
test/present-badges.test.ts
@@ -0,0 +1,122 @@
+import * as assert from 'node:assert'
+import { describe, it } from 'node:test'
+import { Data } from '../src/collect/collect.js'
+import { presentBadges } from '../src/present-badges.js'
+
+describe('present-badges', () => {
+  const data: Data = {
+    user: {} as Data['user'],
+    pulls: [] as Data['pulls'],
+    issues: {} as Data['issues'],
+    repos: [
+      {
+        stargazers_count: 1000,
+        name: 'bar',
+        owner: {
+          login: 'foo',
+        },
+        commits: [],
+      },
+      {
+        stargazers_count: 2000,
+        name: 'qux',
+        owner: {
+          login: 'foo',
+        },
+        commits: [] as any[],
+      },
+    ] as Data['repos'],
+  }
+
+  it('presentBadges() applies `pick`', () => {
+    const userBadges = presentBadges(
+      data,
+      [],
+      ['stars-100', 'stars-500'],
+      [],
+      false,
+    )
+
+    assert.deepEqual(userBadges, [
+      {
+        id: 'stars-100',
+        tier: 1,
+        desc: 'I collected 100 stars.',
+        body:
+          'Repos:\n' +
+          '\n' +
+          '* <a href="https://github.com/foo/bar">foo/bar: ★1000</a>\n' +
+          '\n' +
+          "<sup>I have push, maintainer or admin permissions, so I'm definitely an author.<sup>\n",
+        image:
+          'https://github.com/my-badges/my-badges/blob/master/src/all-badges/pr-collaboration/stars-100.png?raw=true',
+      },
+      {
+        id: 'stars-500',
+        tier: 2,
+        desc: 'I collected 500 stars.',
+        body:
+          'Repos:\n' +
+          '\n' +
+          '* <a href="https://github.com/foo/bar">foo/bar: ★1000</a>\n' +
+          '\n' +
+          "<sup>I have push, maintainer or admin permissions, so I'm definitely an author.<sup>\n",
+        image:
+          'https://github.com/my-badges/my-badges/blob/master/src/all-badges/pr-collaboration/stars-500.png?raw=true',
+      },
+    ])
+  })
+
+  it('presentBadges() applies `omit`', () => {
+    const userBadges = presentBadges(
+      data,
+      [],
+      ['stars-100', 'stars-500'],
+      ['stars-500'],
+      false,
+    )
+
+    assert.deepEqual(userBadges, [
+      {
+        id: 'stars-100',
+        tier: 1,
+        desc: 'I collected 100 stars.',
+        body:
+          'Repos:\n' +
+          '\n' +
+          '* <a href="https://github.com/foo/bar">foo/bar: ★1000</a>\n' +
+          '\n' +
+          "<sup>I have push, maintainer or admin permissions, so I'm definitely an author.<sup>\n",
+        image:
+          'https://github.com/my-badges/my-badges/blob/master/src/all-badges/pr-collaboration/stars-100.png?raw=true',
+      },
+    ])
+  })
+
+  it('presentBadges() applies `compact`', () => {
+    const userBadges = presentBadges(
+      data,
+      [],
+      ['stars-1000', 'stars-2000', 'stars-5000'],
+      [],
+      true,
+    )
+
+    assert.deepEqual(userBadges, [
+      {
+        id: 'stars-2000',
+        tier: 4,
+        desc: 'I collected 2000 stars.',
+        body:
+          'Repos:\n' +
+          '\n' +
+          '* <a href="https://github.com/foo/qux">foo/qux: ★2000</a>\n' +
+          '* <a href="https://github.com/foo/bar">foo/bar: ★1000</a>\n' +
+          '\n' +
+          "<sup>I have push, maintainer or admin permissions, so I'm definitely an author.<sup>\n",
+        image:
+          'https://github.com/my-badges/my-badges/blob/master/src/all-badges/pr-collaboration/stars-2000.png?raw=true',
+      },
+    ])
+  })
+})
test/stars.test.ts
@@ -10,7 +10,7 @@ describe('stars', () => {
     const grant = badgeCollection(badges)
     const data: Data = {
       user: {} as Data['user'],
-      pulls: {} as Data['pulls'],
+      pulls: [] as Data['pulls'],
       issues: {} as Data['issues'],
       repos: [
         {
@@ -19,6 +19,7 @@ describe('stars', () => {
           owner: {
             login: 'foo',
           },
+          commits: [],
         },
         {
           stargazers_count: 2000,
@@ -26,6 +27,7 @@ describe('stars', () => {
           owner: {
             login: 'foo',
           },
+          commits: [] as any[],
         },
       ] as Data['repos'],
     }