98 lines
2.5 KiB
TypeScript
98 lines
2.5 KiB
TypeScript
![]() |
#!/bin/env deno run
|
||
|
|
||
|
import { globber } from "https://deno.land/x/globber@0.1.0/mod.ts"
|
||
|
|
||
|
interface FileIndex {
|
||
|
title: string
|
||
|
slug: string
|
||
|
abstr: string
|
||
|
date: string
|
||
|
readingTimeSlow: number
|
||
|
readingTimeFast: number
|
||
|
}
|
||
|
|
||
|
const cwd = './blog/'
|
||
|
const outputPath = './blog/index.md'
|
||
|
|
||
|
const fileIndex: FileIndex[] = []
|
||
|
|
||
|
const fileIter = globber({ cwd, include: ["????-??-??*.md"] })
|
||
|
const decoder = new TextDecoder('utf-8')
|
||
|
const encoder = new TextEncoder()
|
||
|
|
||
|
const header = `*Sometimes, I write long-form articles about a topic that I find interesting. I use this as a way to dive deeper into a topic, while often create an example project on the side.*
|
||
|
|
||
|
Last updated: ${(new Date()).toISOString().slice(0,10)}
|
||
|
|
||
|
`
|
||
|
|
||
|
const template = `<article class="blog">
|
||
|
<time datetime="%DATE%">%DATE%</time>
|
||
|
<div>
|
||
|
<a href="/blog/%SLUG%.html">%TITLE%</a>
|
||
|
<span class="reading-time">(%READING_TIME_FAST% to %READING_TIME_SLOW% minutes)</span>
|
||
|
</div>
|
||
|
<p>
|
||
|
%ABSTRACT%
|
||
|
</p>
|
||
|
</article>
|
||
|
`
|
||
|
|
||
|
function getAbstract(lines: string[]): string {
|
||
|
const sep = '<!-- more -->'
|
||
|
const sep2 = '<!--more-->'
|
||
|
let foundSep = false
|
||
|
|
||
|
return lines.slice(3).filter(line => {
|
||
|
if (foundSep) return false
|
||
|
if (line.indexOf(sep) >= 0 || line.indexOf(sep2) >= 0) {
|
||
|
foundSep = true
|
||
|
return false
|
||
|
}
|
||
|
return true
|
||
|
}).join('\n').trim()
|
||
|
}
|
||
|
|
||
|
function render(fi: FileIndex) {
|
||
|
return template
|
||
|
.replaceAll('%DATE%', fi.date)
|
||
|
.replaceAll('%TITLE%', fi.title)
|
||
|
.replaceAll('%SLUG%', fi.slug)
|
||
|
.replaceAll('%ABSTRACT%', fi.abstr)
|
||
|
.replaceAll('%READING_TIME_FAST%', fi.readingTimeFast)
|
||
|
.replaceAll('%READING_TIME_SLOW%', fi.readingTimeSlow)
|
||
|
}
|
||
|
|
||
|
for await (const file of fileIter) {
|
||
|
if (file.isDirectory) {
|
||
|
console.log('ignoring directory', file.relative)
|
||
|
break
|
||
|
}
|
||
|
|
||
|
const path = file.absolute
|
||
|
const date = file.relative.slice(0, 10)
|
||
|
const slug = file.relative.slice(0, -3)
|
||
|
|
||
|
const raw = await Deno.readFile(path)
|
||
|
const text = decoder.decode(raw)
|
||
|
const words = text.trim().split(/\s+/).length
|
||
|
const lines = text.split('\n')
|
||
|
|
||
|
const title = lines[0].slice(2)
|
||
|
const abstr = getAbstract(lines)
|
||
|
|
||
|
// calculates estimated reading times: [fast, slow]
|
||
|
// wpm numbers from https://thereadtime.com/
|
||
|
const readingTimeFast = Math.round(words / 207)
|
||
|
const readingTimeSlow = Math.round(words / 167)
|
||
|
|
||
|
fileIndex.push({ title, slug, abstr, date, readingTimeFast, readingTimeSlow })
|
||
|
}
|
||
|
|
||
|
const output = fileIndex
|
||
|
.sort((a, b) => a.date.localeCompare(b.date) * -1)
|
||
|
.map(render)
|
||
|
.join('\n')
|
||
|
|
||
|
Deno.writeFile(outputPath, encoder.encode(header+output))
|