X-Git-Url: https://git.r.bdr.sh/rbdr/blog/blobdiff_plain/5e265f9d81bcce30949e88850892ea5dacec7386..6f72ad0f8b1d147a3d2e27db539a0e17ca2ac917:/lib/blog.js?ds=inline diff --git a/lib/blog.js b/lib/blog.js index 483577f..6b3397f 100644 --- a/lib/blog.js +++ b/lib/blog.js @@ -1,27 +1,20 @@ 'use strict'; -const Fs = require('fs'); -const Markdown = require('markdown'); -const Mustache = require('mustache'); -const Ncp = require('ncp'); -const Path = require('path'); -const Rimraf = require('rimraf'); -const Util = require('util'); +const { exec } = require('child_process'); +const { access, mkdir, readdir, readFile, rmdir, writeFile } = require('fs/promises'); +const { template } = require('dot'); +const { ncp } = require('ncp'); +const { join } = require('path'); +const Marked = require('marked'); +const { debuglog, promisify } = require('util'); const internals = { // Promisified functions + ncp: promisify(ncp), - fs: { - access: Util.promisify(Fs.access), - mkdir: Util.promisify(Fs.mkdir), - readdir: Util.promisify(Fs.readdir), - readFile: Util.promisify(Fs.readFile), - writeFile: Util.promisify(Fs.writeFile) - }, - ncp: Util.promisify(Ncp.ncp), - rimraf: Util.promisify(Rimraf), - debuglog: Util.debuglog('blog'), + exec: promisify(exec), + debuglog: debuglog('blog'), // constants @@ -29,6 +22,7 @@ const internals = { kIndexName: 'index.html', kFileNotFoundError: 'ENOENT', kMarkdownRe: /\.md$/i, + kMetadataFilename: 'metadata.json', // Strings @@ -65,6 +59,7 @@ module.exports = class Blog { */ async add(postLocation) { + await this._ensurePostsDirectoryExists(); await this._shift(); await this.update(postLocation); } @@ -81,7 +76,11 @@ module.exports = class Blog { */ async update(postLocation) { + const metadata = await this._getMetadata(); + await this._ensurePostsDirectoryExists(); await this._copyPost(postLocation); + await this._writeMetadata(metadata); + await this._generate(); } @@ -93,64 +92,77 @@ module.exports = class Blog { * @return {Promise} empty promise, returns no value * @instance */ - async publish() { + publish() { console.error('Publishing not yet implemented'); + return Promise.resolve(); } // Parses markdown for each page, copies assets and generates index. async _generate() { - const assetsTarget = Path.join(this.staticDirectory, internals.kAssetsDirectoryName); - const indexTarget = Path.join(this.staticDirectory, internals.kIndexName); - const indexLocation = Path.join(this.templatesDirectory, internals.kIndexName); + const assetsTarget = join(this.staticDirectory, internals.kAssetsDirectoryName); + const indexTarget = join(this.staticDirectory, internals.kIndexName); + const indexLocation = join(this.templatesDirectory, internals.kIndexName); const posts = []; internals.debuglog(`Removing ${assetsTarget}`); - await internals.rimraf(assetsTarget); + await rmdir(assetsTarget, { recursive: true }); for (let i = 0; i < this.maxPosts; ++i) { - const sourcePath = Path.join(this.postsDirectory, `${i}`); - const assetsSource = Path.join(sourcePath, internals.kAssetsDirectoryName); - const postContentPath = await this._findBlogContent(sourcePath); + const sourcePath = join(this.postsDirectory, `${i}`); + + try { + await access(this.postsDirectory); + + const assetsSource = join(sourcePath, internals.kAssetsDirectoryName); + const postContentPath = await this._findBlogContent(sourcePath); - internals.debuglog(`Copying ${assetsSource} to ${assetsTarget}`); - await internals.ncp(assetsSource, assetsTarget); + internals.debuglog(`Copying ${assetsSource} to ${assetsTarget}`); + await internals.ncp(assetsSource, assetsTarget); - internals.debuglog(`Reading ${postContentPath}`); - const postContent = await internals.fs.readFile(postContentPath, { encoding: 'utf8' }); + internals.debuglog(`Reading ${postContentPath}`); + const postContent = await readFile(postContentPath, { encoding: 'utf8' }); + + internals.debuglog('Parsing markdown'); + posts.push({ + html: Marked(postContent), + id: i + 1 + }); + } + catch (error) { + if (error.code === internals.kFileNotFoundError) { + internals.debuglog(`Skipping ${i}`); + continue; + } - internals.debuglog('Parsing markdown'); - posts.push({ - html: Markdown.markdown.toHTML(postContent), - id: i + 1 - }); + throw error; + } } internals.debuglog(`Reading ${indexLocation}`); - const indexTemplate = await internals.fs.readFile(indexLocation, { encoding: 'utf8' }); + const indexTemplate = await readFile(indexLocation, { encoding: 'utf8' }); internals.debuglog('Generating HTML'); - const indexHtml = Mustache.render(indexTemplate, { posts }); - await internals.fs.writeFile(indexTarget, indexHtml); + const indexHtml = template(indexTemplate)({ posts }); + await writeFile(indexTarget, indexHtml); } // Shift the posts, delete any remainder. async _shift() { - await this._ensurePostsDirectoryExists(); - for (let i = this.maxPosts - 1; i > 0; --i) { - const targetPath = Path.join(this.postsDirectory, `${i}`); - const sourcePath = Path.join(this.postsDirectory, `${i - 1}`); + for (let i = this.maxPosts - 1; i >= 0; --i) { + const targetPath = join(this.postsDirectory, `${i}`); + const sourcePath = join(this.postsDirectory, `${i - 1}`); try { - await internals.fs.access(sourcePath); - internals.debuglog(`Removing ${targetPath}`); - await internals.rimraf(targetPath); + await rmdir(targetPath, { recursive: true }); + + await access(sourcePath); // check the source path internals.debuglog(`Shifting blog post ${sourcePath} to ${targetPath}`); await internals.ncp(sourcePath, targetPath); @@ -166,16 +178,46 @@ module.exports = class Blog { } } + // Attempts to read existing metadata. Otherwise generates new set. + + async _getMetadata() { + + const metadataTarget = join(this.postsDirectory, '0', internals.kMetadataFilename); + + try { + internals.debuglog(`Looking for metadata at ${metadataTarget}`); + return await readFile(metadataTarget); + } + catch (e) { + internals.debuglog(`Metadata not found or unreadable. Generating new set.`); + const createdOn = Date.now(); + const metadata = { + id: String(createdOn), + createdOn + }; + + return JSON.stringify(metadata, null, 2); + } + } + + // Writes metadata. Assumes post 0 since it only gets written + // on create + + async _writeMetadata(metadata) { + + const metadataTarget = join(this.postsDirectory, '0', internals.kMetadataFilename); + internals.debuglog(`Writing ${metadataTarget}`); + await writeFile(metadataTarget, metadata); + } + // Copies a post directory to the latest slot. async _copyPost(postLocation) { - await this._ensurePostsDirectoryExists(); - - const targetPath = Path.join(this.postsDirectory, '0'); + const targetPath = join(this.postsDirectory, '0'); internals.debuglog(`Removing ${targetPath}`); - await internals.rimraf(targetPath); + await rmdir(targetPath, { recursive: true }); internals.debuglog(`Adding ${postLocation} to ${targetPath}`); await internals.ncp(postLocation, targetPath); @@ -187,12 +229,12 @@ module.exports = class Blog { internals.debuglog(`Checking if ${this.postsDirectory} exists.`); try { - await internals.fs.access(this.postsDirectory); + await access(this.postsDirectory); } catch (error) { if (error.code === internals.kFileNotFoundError) { internals.debuglog('Creating posts directory'); - await internals.fs.mkdir(this.postsDirectory); + await mkdir(this.postsDirectory); return; } @@ -204,11 +246,11 @@ module.exports = class Blog { async _findBlogContent(directory) { - const entries = await internals.fs.readdir(directory); + const entries = await readdir(directory); const markdownEntries = entries .filter((entry) => internals.kMarkdownRe.test(entry)) - .map((entry) => Path.join(directory, entry)); + .map((entry) => join(directory, entry)); if (markdownEntries.length > 0) { internals.debuglog(`Found markdown file: ${markdownEntries[0]}`);