diff options
| author | Ben Beltran <ben@nsovocal.com> | 2020-06-02 21:34:29 +0200 |
|---|---|---|
| committer | Ben Beltran <ben@nsovocal.com> | 2020-06-02 21:34:29 +0200 |
| commit | db7b464d701c7d48777ddd38a10f69093cd5442c (patch) | |
| tree | 788d01018ac3190b1e5d8f4f26e8fd379d6a74fa /lib | |
| parent | 863ccf104e7cae08268b9e46896b82a9918a22fd (diff) | |
| parent | 04b71c5478dea42937a3288aed9598663c6dab0d (diff) | |
Merge branch 'feature/rbdr-rss-support' into develop
Diffstat (limited to 'lib')
| -rw-r--r-- | lib/blog.js | 103 | ||||
| -rw-r--r-- | lib/generators/html.js | 35 | ||||
| -rw-r--r-- | lib/generators/rss.js | 42 | ||||
| -rw-r--r-- | lib/generators/static.js | 50 |
4 files changed, 196 insertions, 34 deletions
diff --git a/lib/blog.js b/lib/blog.js index 9024a00..98efabd 100644 --- a/lib/blog.js +++ b/lib/blog.js @@ -1,28 +1,27 @@ 'use strict'; -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 StaticGenerator = require('./generators/static'); +const HTMLGenerator = require('./generators/html'); +const RSSGenerator = require('./generators/rss'); + const internals = { // Promisified functions ncp: promisify(ncp), - exec: promisify(exec), debuglog: debuglog('blog'), // constants - kAssetsDirectoryName: 'assets', - kIndexName: 'index.html', kFileNotFoundError: 'ENOENT', kMarkdownRe: /\.md$/i, - kRemoveCommand: 'rm -rf', + kMetadataFilename: 'metadata.json', // Strings @@ -36,7 +35,7 @@ const internals = { * updating posts, and handling the publishing. * * @class Blog - * @param {Potluck.tConfiguration} config the initialization options to + * @param {Blog.tConfiguration} config the initialization options to * extend the instance */ module.exports = class Blog { @@ -59,6 +58,7 @@ module.exports = class Blog { */ async add(postLocation) { + await this._ensurePostsDirectoryExists(); await this._shift(); await this.update(postLocation); } @@ -75,7 +75,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(); } @@ -97,33 +101,40 @@ module.exports = class Blog { async _generate() { - 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('Generating output'); + + const posts = await this._readPosts(this.postsDirectory); + + await StaticGenerator(this.postsDirectory, this.staticDirectory, posts); + await HTMLGenerator(this.templatesDirectory, this.staticDirectory, posts); + await RSSGenerator(this.templatesDirectory, this.staticDirectory, posts); + } - internals.debuglog(`Removing ${assetsTarget}`); - await rmdir(assetsTarget, { recursive: true }); + // Reads the posts into an array + + async _readPosts(source) { + + internals.debuglog('Reading posts'); + const posts = []; for (let i = 0; i < this.maxPosts; ++i) { - const sourcePath = join(this.postsDirectory, `${i}`); + const postSourcePath = join(source, `${i}`); - try { - await access(this.postsDirectory); + internals.debuglog(`Reading ${postSourcePath} into posts array`); - const assetsSource = join(sourcePath, internals.kAssetsDirectoryName); - const postContentPath = await this._findBlogContent(sourcePath); + try { + await access(postSourcePath); - internals.debuglog(`Copying ${assetsSource} to ${assetsTarget}`); - await internals.ncp(assetsSource, assetsTarget); + const metadata = await this._getMetadata(i); + const postContentPath = await this._findBlogContent(postSourcePath); internals.debuglog(`Reading ${postContentPath}`); const postContent = await readFile(postContentPath, { encoding: 'utf8' }); internals.debuglog('Parsing markdown'); posts.push({ - html: Marked(postContent), - id: i + 1 + ...metadata, + html: Marked(postContent) }); } catch (error) { @@ -136,30 +147,24 @@ module.exports = class Blog { } } - internals.debuglog(`Reading ${indexLocation}`); - const indexTemplate = await readFile(indexLocation, { encoding: 'utf8' }); - - internals.debuglog('Generating HTML'); - const indexHtml = template(indexTemplate)({ posts }); - await writeFile(indexTarget, indexHtml); + return posts; } // Shift the posts, delete any remainder. async _shift() { - await this._ensurePostsDirectoryExists(); - for (let i = this.maxPosts - 1; i > 0; --i) { + for (let i = this.maxPosts - 1; i >= 0; --i) { const targetPath = join(this.postsDirectory, `${i}`); const sourcePath = join(this.postsDirectory, `${i - 1}`); try { - await access(sourcePath); - internals.debuglog(`Removing ${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); } @@ -174,12 +179,42 @@ module.exports = class Blog { } } + // Attempts to read existing metadata. Otherwise generates new set. + + async _getMetadata(index = 0) { + + const metadataTarget = join(this.postsDirectory, String(index), internals.kMetadataFilename); + + try { + internals.debuglog(`Looking for metadata at ${metadataTarget}`); + return JSON.parse(await readFile(metadataTarget, { encoding: 'utf8' })); + } + catch (e) { + internals.debuglog(`Metadata not found or unreadable. Generating new set.`); + const createdOn = Date.now(); + const metadata = { + id: String(createdOn), + createdOn + }; + + return metadata; + } + } + + // 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, JSON.stringify(metadata, null, 2)); + } + // Copies a post directory to the latest slot. async _copyPost(postLocation) { - await this._ensurePostsDirectoryExists(); - const targetPath = join(this.postsDirectory, '0'); internals.debuglog(`Removing ${targetPath}`); diff --git a/lib/generators/html.js b/lib/generators/html.js new file mode 100644 index 0000000..ba2676c --- /dev/null +++ b/lib/generators/html.js @@ -0,0 +1,35 @@ +'use strict'; + +const { template } = require('dot'); +const { readFile, writeFile } = require('fs/promises'); +const { join } = require('path'); +const { debuglog } = require('util'); + +const internals = { + debuglog: debuglog('blog'), + + kIndexName: 'index.html' +}; + +/** + * Generates the blog index page + * + * @name HTMLGenerator + * @param {string} source the source directory + * @param {string} target the target directory + * @param {Array.<Blog.tPost>} posts the list of posts + */ +module.exports = async function HTMLGenerator(source, target, posts) { + + internals.debuglog('Generating HTML'); + const indexTarget = join(target, internals.kIndexName); + const indexLocation = join(source, internals.kIndexName); + + internals.debuglog(`Reading ${indexLocation}`); + const indexTemplate = await readFile(indexLocation, { encoding: 'utf8' }); + + internals.debuglog('Writing HTML'); + const indexHtml = template(indexTemplate)({ posts }); + await writeFile(indexTarget, indexHtml); +}; + diff --git a/lib/generators/rss.js b/lib/generators/rss.js new file mode 100644 index 0000000..a1fcb08 --- /dev/null +++ b/lib/generators/rss.js @@ -0,0 +1,42 @@ +'use strict'; + +const { template } = require('dot'); +const { encodeXML } = require('entities'); +const { readFile, writeFile } = require('fs/promises'); +const { join } = require('path'); +const { debuglog } = require('util'); + +const internals = { + debuglog: debuglog('blog'), + + kFeedName: 'feed.xml' +}; + +/** + * Generates an RSS feed XML file + * + * @name RSSGenerator + * @param {string} source the source directory + * @param {string} target the target directory + * @param {Array.<Blog.tPost>} posts the list of posts + */ +module.exports = async function RSSGenerator(source, target, posts) { + + internals.debuglog('Generating RSS'); + const feedTarget = join(target, internals.kFeedName); + const feedLocation = join(source, internals.kFeedName); + + internals.debuglog(`Reading ${feedLocation}`); + const feedTemplate = await readFile(feedLocation, { encoding: 'utf8' }); + + internals.debuglog('Writing RSS'); + posts = posts.map((post) => ({ + ...post, + createdOn: (new Date(post.createdOn)).toUTCString(), + html: encodeXML(post.html) + })); + const feedXml = template(feedTemplate)({ posts }); + await writeFile(feedTarget, feedXml); +}; + + diff --git a/lib/generators/static.js b/lib/generators/static.js new file mode 100644 index 0000000..eb3c631 --- /dev/null +++ b/lib/generators/static.js @@ -0,0 +1,50 @@ +'use strict'; + +const { access, rmdir } = require('fs/promises'); +const { ncp } = require('ncp'); +const { join } = require('path'); +const { debuglog, promisify } = require('util'); + +const internals = { + ncp: promisify(ncp), + debuglog: debuglog('blog'), + + kAssetsDirectoryName: 'assets' +}; + +/** + * Generates the static assets required for the blog + * + * @name StaticGenerator + * @param {string} source the source directory + * @param {string} target the target directory + * @param {Array.<Blog.tPost>} posts the list of posts + */ +module.exports = async function StaticGenerator(source, target, posts) { + + const assetsTarget = join(target, internals.kAssetsDirectoryName); + + internals.debuglog(`Removing ${assetsTarget}`); + await rmdir(assetsTarget, { recursive: true }); + + for (let i = 0; i < posts.length; ++i) { + const postSourcePath = join(source, `${i}`); + + try { + await access(postSourcePath); + + const assetsSource = join(postSourcePath, internals.kAssetsDirectoryName); + + internals.debuglog(`Copying ${assetsSource} to ${assetsTarget}`); + await internals.ncp(assetsSource, assetsTarget); + } + catch (error) { + if (error.code === internals.kFileNotFoundError) { + internals.debuglog(`Skipping ${i}`); + continue; + } + + throw error; + } + } +}; |