The great refactor (#82)
This commit is contained in:
9
source/templates/README.md
Normal file
9
source/templates/README.md
Normal file
@@ -0,0 +1,9 @@
|
||||
## 🖼️ Templates
|
||||
|
||||
Templates lets you change general appearance of rendered metrics.
|
||||
See their respective documentation for more informations about how to setup them:
|
||||
|
||||
* [📗 Classic](/source/templates/classic/README.md)
|
||||
* [📘 Repository](/source/templates/repository/README.md)
|
||||
* [📙 Terminal](/source/templates/terminal/README.md)
|
||||
* [📕 Community templates](/source/templates/community/README.md)
|
||||
19
source/templates/classic/README.md
Normal file
19
source/templates/classic/README.md
Normal file
@@ -0,0 +1,19 @@
|
||||
### 📗 Classic
|
||||
|
||||
Default template, mimicking GitHub visual identity.
|
||||
|
||||
<table>
|
||||
<td align="center">
|
||||
<img src="https://github.com/lowlighter/lowlighter/blob/master/metrics.classic.svg">
|
||||
<img width="900" height="1" alt="">
|
||||
</td>
|
||||
</table>
|
||||
|
||||
#### ℹ️ Examples workflows
|
||||
|
||||
```yaml
|
||||
- uses: lowlighter/metrics@latest
|
||||
with:
|
||||
# ... other options
|
||||
template: classic
|
||||
```
|
||||
1
source/templates/classic/partials/lines.ejs
Normal file
1
source/templates/classic/partials/lines.ejs
Normal file
@@ -0,0 +1 @@
|
||||
<%# Included in base.repositories.ejs %>
|
||||
1
source/templates/classic/partials/traffic.ejs
Normal file
1
source/templates/classic/partials/traffic.ejs
Normal file
@@ -0,0 +1 @@
|
||||
<%# Included in base.repositories.ejs %>
|
||||
@@ -1,8 +1,5 @@
|
||||
//Imports
|
||||
import common from "./../common.mjs"
|
||||
|
||||
/** Template processor */
|
||||
export default async function ({login, q}, {conf, data, rest, graphql, plugins, queries}, {s, pending, imports}) {
|
||||
//Common
|
||||
await common(...arguments)
|
||||
//Core
|
||||
await imports.plugins.core(...arguments)
|
||||
}
|
||||
@@ -1,121 +0,0 @@
|
||||
/** Template common processor */
|
||||
export default async function ({login, q, dflags}, {conf, data, rest, graphql, plugins, queries, account}, {s, pending, imports}) {
|
||||
|
||||
//Init
|
||||
const computed = data.computed = {commits:0, sponsorships:0, licenses:{favorite:"", used:{}}, token:{}, repositories:{watchers:0, stargazers:0, issues_open:0, issues_closed:0, pr_open:0, pr_merged:0, forks:0, forked:0, releases:0}}
|
||||
const avatar = imports.imgb64(data.user.avatarUrl)
|
||||
console.debug(`metrics/compute/${login} > formatting common metrics`)
|
||||
|
||||
//Timezone config
|
||||
if (q["config.timezone"]) {
|
||||
const timezone = data.config.timezone = {name:q["config.timezone"], offset:0}
|
||||
try {
|
||||
timezone.offset = Number(new Date().toLocaleString("fr", {timeZoneName:"short", timeZone:timezone.name}).match(/UTC[+](?<offset>\d+)/)?.groups?.offset*60*60*1000) || 0
|
||||
console.debug(`metrics/compute/${login} > timezone set to ${timezone.name} (${timezone.offset > 0 ? "+" : ""}${Math.round(timezone.offset/(60*60*1000))} hours)`)
|
||||
} catch {
|
||||
timezone.error = `Failed to use timezone "${timezone.name}"`
|
||||
console.debug(`metrics/compute/${login} > failed to use timezone "${timezone.name}"`)
|
||||
}
|
||||
}
|
||||
|
||||
//Animations
|
||||
if ("config.animations" in q) {
|
||||
data.animated = q["config.animations"]
|
||||
console.debug(`metrics/compute/${login} > animations ${data.animated ? "enabled" : "disabled"}`)
|
||||
}
|
||||
|
||||
//Plugins
|
||||
for (const name of Object.keys(imports.plugins)) {
|
||||
if (!plugins[name]?.enabled)
|
||||
continue
|
||||
|
||||
pending.push((async () => {
|
||||
try {
|
||||
console.debug(`metrics/compute/${login}/plugins > ${name} > started`)
|
||||
data.plugins[name] = await imports.plugins[name]({login, q, imports, data, computed, rest, graphql, queries, account}, plugins[name])
|
||||
console.debug(`metrics/compute/${login}/plugins > ${name} > completed`)
|
||||
}
|
||||
catch (error) {
|
||||
console.debug(`metrics/compute/${login}/plugins > ${name} > completed (error)`)
|
||||
data.plugins[name] = error
|
||||
}
|
||||
finally {
|
||||
const result = {name, result:data.plugins[name]}
|
||||
console.debug(imports.util.inspect(result, {depth:Infinity, maxStringLength:256}))
|
||||
return result
|
||||
}
|
||||
})())
|
||||
}
|
||||
|
||||
//Iterate through user's repositories
|
||||
for (const repository of data.user.repositories.nodes) {
|
||||
//Simple properties with totalCount
|
||||
for (const property of ["watchers", "stargazers", "issues_open", "issues_closed", "pr_open", "pr_merged", "releases"])
|
||||
computed.repositories[property] += repository[property].totalCount
|
||||
//Forks
|
||||
computed.repositories.forks += repository.forkCount
|
||||
if (repository.isFork)
|
||||
computed.repositories.forked++
|
||||
//License
|
||||
if (repository.licenseInfo)
|
||||
computed.licenses.used[repository.licenseInfo.spdxId] = (computed.licenses.used[repository.licenseInfo.spdxId] ?? 0) + 1
|
||||
}
|
||||
|
||||
//Total disk usage
|
||||
computed.diskUsage = `${imports.bytes(data.user.repositories.totalDiskUsage*1000)}`
|
||||
|
||||
//Compute licenses stats
|
||||
computed.licenses.favorite = Object.entries(computed.licenses.used).sort(([an, a], [bn, b]) => b - a).slice(0, 1).map(([name, value]) => name) ?? ""
|
||||
|
||||
//Compute total commits
|
||||
computed.commits += data.user.contributionsCollection.totalCommitContributions + data.user.contributionsCollection.restrictedContributionsCount
|
||||
|
||||
//Compute registration date
|
||||
const diff = (Date.now()-(new Date(data.user.createdAt)).getTime())/(365*24*60*60*1000)
|
||||
const years = Math.floor(diff)
|
||||
const months = Math.floor((diff-years)*12)
|
||||
computed.registration = years ? `${years} year${s(years)} ago` : months ? `${months} month${s(months)} ago` : `${Math.ceil(diff*365)} day${s(Math.ceil(diff*365))} ago`
|
||||
computed.cakeday = years > 1 ? [new Date(), new Date(data.user.createdAt)].map(date => date.toISOString().match(/(?<mmdd>\d{2}-\d{2})(?=T)/)?.groups?.mmdd).every((v, _, a) => v === a[0]) : false
|
||||
|
||||
//Compute calendar
|
||||
computed.calendar = data.user.calendar.contributionCalendar.weeks.flatMap(({contributionDays}) => contributionDays).slice(0, 14).reverse()
|
||||
|
||||
//Avatar (base64)
|
||||
computed.avatar = await avatar || "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg=="
|
||||
|
||||
//Token scopes
|
||||
computed.token.scopes = (await rest.request("HEAD /")).headers["x-oauth-scopes"].split(", ")
|
||||
|
||||
//Meta
|
||||
data.meta = {version:conf.package.version, author:conf.package.author}
|
||||
|
||||
//Debug flags
|
||||
if ((dflags.includes("--cakeday"))||(q["dflag.cakeday"])) {
|
||||
console.debug(`metrics/compute/${login} > applying dflag --cakeday`)
|
||||
computed.cakeday = true
|
||||
}
|
||||
if ((dflags.includes("--hireable"))||(q["dflag.hireable"])) {
|
||||
console.debug(`metrics/compute/${login} > applying dflag --hireable`)
|
||||
data.user.isHireable = true
|
||||
}
|
||||
if ((dflags.includes("--halloween"))||(q["dflag.halloween"])) {
|
||||
console.debug(`metrics/compute/${login} > applying dflag --halloween`)
|
||||
//Haloween color replacer
|
||||
const halloween = content => content
|
||||
.replace(/--color-calendar-graph/g, "--color-calendar-halloween-graph")
|
||||
.replace(/#9be9a8/gi, "var(--color-calendar-halloween-graph-day-L1-bg)")
|
||||
.replace(/#40c463/gi, "var(--color-calendar-halloween-graph-day-L2-bg)")
|
||||
.replace(/#30a14e/gi, "var(--color-calendar-halloween-graph-day-L3-bg)")
|
||||
.replace(/#216e39/gi, "var(--color-calendar-halloween-graph-day-L4-bg)")
|
||||
//Update contribution calendar colors
|
||||
computed.calendar.map(day => day.color = halloween(day.color))
|
||||
//Update isocalendar colors
|
||||
const waiting = [...pending]
|
||||
pending.push((async () => {
|
||||
await Promise.all(waiting)
|
||||
if (data.plugins.isocalendar?.svg)
|
||||
data.plugins.isocalendar.svg = halloween(data.plugins.isocalendar.svg)
|
||||
return {name:"dflag.halloween", result:true}
|
||||
})())
|
||||
}
|
||||
}
|
||||
25
source/templates/community/README.md
Normal file
25
source/templates/community/README.md
Normal file
@@ -0,0 +1,25 @@
|
||||
### 📕 Community templates
|
||||
|
||||
It is possible to use official releases with templates from forked repositories (whether you own them or not).
|
||||
|
||||
Use `setup_community_templates` option to specify additional external sources using following format: `user/repo@branch:template`.
|
||||
Templates added this way will be downloaded through git and can be used by prefixing their name with an `@`.
|
||||
|
||||
By default, community templates use `template.mjs` from official `classic` template instead of their own, to prevent executing malicious code and avoid token leaks.
|
||||
|
||||
If you trust it, append `+trust` after their name.
|
||||
|
||||
```yaml
|
||||
- uses: lowlighter/metrics@master
|
||||
with:
|
||||
# ... other options
|
||||
template: "@super-metrics"
|
||||
# Download "super-metrics" and "trusted-metrics" templates from "octocat/metrics@master"
|
||||
# "@trusted-metrics" template can execute remote JavaScript code
|
||||
setup_community_templates: octocat/metrics@master:super-metrics, octocat/metrics@master:trusted-metrics+trust
|
||||
```
|
||||
|
||||
To create a new community template, fork this repository and create a new folder in `/source/templates` with same structure as current templates.
|
||||
Then, it's just as simple as HTML and CSS with a bit of JavaScript!
|
||||
|
||||
If you made something awesome, please share it here!
|
||||
21
source/templates/repository/README.md
Normal file
21
source/templates/repository/README.md
Normal file
@@ -0,0 +1,21 @@
|
||||
### 📘 Repository
|
||||
|
||||
Template crafted for repositories, mimicking GitHub visual identity.
|
||||
|
||||
<table>
|
||||
<td align="center">
|
||||
<img src="https://github.com/lowlighter/lowlighter/blob/master/metrics.repository.svg">
|
||||
<img width="900" height="1" alt="">
|
||||
</td>
|
||||
</table>
|
||||
|
||||
#### ℹ️ Examples workflows
|
||||
|
||||
```yaml
|
||||
- uses: lowlighter/metrics@latest
|
||||
with:
|
||||
# ... other options
|
||||
template: classic
|
||||
user: repository-owner # Optional if you're the owner of target repository
|
||||
query: '{"repo":"repository-name"}' # Use a JSON encoded object to pass your repository name in "repo" key
|
||||
```
|
||||
1
source/templates/repository/partials/lines.ejs
Normal file
1
source/templates/repository/partials/lines.ejs
Normal file
@@ -0,0 +1 @@
|
||||
<%# Included in base.repositories.ejs %>
|
||||
1
source/templates/repository/partials/traffic.ejs
Normal file
1
source/templates/repository/partials/traffic.ejs
Normal file
@@ -0,0 +1 @@
|
||||
<%# Included in base.repositories.ejs %>
|
||||
@@ -1,6 +1,3 @@
|
||||
//Imports
|
||||
import common from "./../common.mjs"
|
||||
|
||||
/** Template processor */
|
||||
export default async function ({login, q}, {conf, data, rest, graphql, plugins, queries, account}, {s, pending, imports}) {
|
||||
//Check arguments
|
||||
@@ -8,13 +5,13 @@
|
||||
if (!repo) {
|
||||
console.debug(`metrics/compute/${login}/${repo} > error, repo was undefined`)
|
||||
data.errors.push({error:{message:`You must pass a "repo" argument to use this template`}})
|
||||
return await common(...arguments)
|
||||
return await imports.plugins.core(...arguments)
|
||||
}
|
||||
console.debug(`metrics/compute/${login}/${repo} > switching to mode ${account}`)
|
||||
|
||||
//Retrieving single repository
|
||||
console.debug(`metrics/compute/${login}/${repo} > retrieving single repository ${repo}`)
|
||||
const {[account]:{repository}} = await graphql(queries.repository({login, repo, account}))
|
||||
const {[account]:{repository}} = await graphql(queries.base.repository({login, repo, account}))
|
||||
data.user.repositories.nodes = [repository]
|
||||
data.repo = repository
|
||||
|
||||
@@ -66,8 +63,8 @@
|
||||
//Override plugins parameters
|
||||
q["projects.limit"] = 0
|
||||
|
||||
//Common
|
||||
await common(...arguments)
|
||||
//Core
|
||||
await imports.plugins.core(...arguments)
|
||||
await Promise.all(pending)
|
||||
|
||||
//Set repository name
|
||||
|
||||
19
source/templates/terminal/README.md
Normal file
19
source/templates/terminal/README.md
Normal file
@@ -0,0 +1,19 @@
|
||||
### 📙 Terminal
|
||||
|
||||
Terminal template, mimicking a SSH session.
|
||||
|
||||
<table>
|
||||
<td align="center">
|
||||
<img src="https://github.com/lowlighter/lowlighter/blob/master/metrics.terminal.svg">
|
||||
<img width="900" height="1" alt="">
|
||||
</td>
|
||||
</table>
|
||||
|
||||
#### ℹ️ Examples workflows
|
||||
|
||||
```yaml
|
||||
- uses: lowlighter/metrics@latest
|
||||
with:
|
||||
# ... other options
|
||||
template: terminal
|
||||
```
|
||||
1
source/templates/terminal/partials/lines.ejs
Normal file
1
source/templates/terminal/partials/lines.ejs
Normal file
@@ -0,0 +1 @@
|
||||
<%# Included in base.repositories.ejs %>
|
||||
1
source/templates/terminal/partials/traffic.ejs
Normal file
1
source/templates/terminal/partials/traffic.ejs
Normal file
@@ -0,0 +1 @@
|
||||
<%# Included in base.repositories.ejs %>
|
||||
@@ -1,10 +1,7 @@
|
||||
//Imports
|
||||
import common from "./../common.mjs"
|
||||
|
||||
/** Template processor */
|
||||
export default async function ({login, q}, {conf, data, rest, graphql, plugins, queries}, {s, pending, imports}) {
|
||||
//Common
|
||||
await common(...arguments)
|
||||
//Core
|
||||
await imports.plugins.core(...arguments)
|
||||
//Disable optimization to keep white-spaces
|
||||
q.raw = true
|
||||
}
|
||||
Reference in New Issue
Block a user