From 6cd62e795e3716aa0cbd2d1ff8c1b3a345803563 Mon Sep 17 00:00:00 2001 From: Ruben Beltran del Rio Date: Wed, 14 Feb 2024 16:58:56 +0100 Subject: Use modules, use XDG dirs --- lib/archivers/gemlog.js | 47 ++++++++++++++----------- lib/blog.js | 92 +++++++++++++++++++++++++++++++----------------- lib/constants.js | 4 +-- lib/generators/html.js | 14 ++++---- lib/generators/rss.js | 16 ++++----- lib/generators/static.js | 50 +++++++++++++------------- lib/generators/txt.js | 15 ++++---- lib/remote.js | 10 +++--- lib/remotes/git.js | 6 ++-- lib/utils.js | 48 ++++++++++++------------- 10 files changed, 165 insertions(+), 137 deletions(-) (limited to 'lib') diff --git a/lib/archivers/gemlog.js b/lib/archivers/gemlog.js index 3443c66..d56a1f6 100644 --- a/lib/archivers/gemlog.js +++ b/lib/archivers/gemlog.js @@ -1,10 +1,10 @@ -const { cp, mkdir, readdir, writeFile } = require('fs/promises'); -const { debuglog } = require('util'); -const { resolve, join } = require('path'); -const { rmIfExists } = require('../utils'); +import Dot from 'dot'; +import { cp, mkdir, readdir, readFile, writeFile } from 'fs/promises'; +import { debuglog } from 'util'; +import { join } from 'path'; +import { rmIfExists } from '../utils.js'; const internals = { - kArchiveName: resolve(join(__dirname, '../..', '.gemlog')), kIndexName: 'index.gmi', kGeminiRe: /\.gmi$/i, @@ -26,13 +26,13 @@ const internals = { } }; -module.exports = async function(archiveDirectory) { - internals.debuglog(`Reading archive ${archiveDirectory}`); - const postIds = (await readdir(archiveDirectory)) +export default async function(templateDirectory, source, target) { + internals.debuglog(`Reading archive ${source}`); + const postIds = (await readdir(source)) .sort((a, b) => Number(b) - Number(a)); const posts = []; for (const id of postIds) { - const postDirectory = join(archiveDirectory, id); + const postDirectory = join(source, id); const slug = (await readdir(postDirectory)) .filter((entry) => internals.kGeminiRe.test(entry))[0]; @@ -41,26 +41,31 @@ module.exports = async function(archiveDirectory) { internals.debuglog(`Read ${posts.length} posts`); - const index = [ - '# Ruben\'s Gemlog Archive', '', - '=> https://r.bdr.sh/gemlog/feed.xml 📰 RSS Feed', - '=> https://r.bdr.sh/gemlog/index.txt 📑 http text version (latest 3 posts)', - '', - ...posts.map((post) => internals.buildLink(post.id, post.slug)), - '', '=> ../ ðŸŠī Back to main page' - ].join('\n'); + internals.debuglog('Generating Archive Index'); + const indexLocation = join(templateDirectory, internals.kIndexName); + + internals.debuglog(`Reading ${indexLocation}`); + const indexTemplate = await readFile(indexLocation, { encoding: 'utf8' }); + + internals.debuglog('Writing Archive Index'); + const index = Dot.template(indexTemplate, { + ...Dot.templateSettings, + strip: false + })({ + posts: posts.map((post) => internals.buildLink(post.id, post.slug)).join('\n'), + }); try { internals.debuglog('Removing index'); - await rmIfExists(internals.kArchiveName); + await rmIfExists(target); } finally { internals.debuglog('Creating index'); - await mkdir(internals.kArchiveName); - const indexFile = join(internals.kArchiveName, internals.kIndexName); + await mkdir(target); + const indexFile = join(target, internals.kIndexName); await writeFile(indexFile, index); internals.debuglog('Copying posts to archive'); - await cp(archiveDirectory, internals.kArchiveName, { recursive: true }); + await cp(source, target, { recursive: true }); } }; diff --git a/lib/blog.js b/lib/blog.js index ca944cf..04106b0 100644 --- a/lib/blog.js +++ b/lib/blog.js @@ -1,28 +1,26 @@ -'use strict'; - -const { access, cp, mkdir, readdir, readFile, writeFile } = require('fs/promises'); -const { exec } = require('child_process'); -const { basename, resolve, join } = require('path'); -const ParseGemini = require('gemini-to-html/parse'); -const RenderGemini = require('gemini-to-html/render'); -const { debuglog, promisify } = require('util'); -const { ensureDirectoryExists, rmIfExists } = require('./utils'); -const { kFileNotFoundError } = require('./constants'); +import { access, cp, readdir, readFile, writeFile } from 'fs/promises'; +import { exec } from 'child_process'; +import { basename, resolve, join } from 'path'; +import ParseGemini from 'gemini-to-html/parse.js'; +import RenderGemini from 'gemini-to-html/render.js'; +import { debuglog, promisify } from 'util'; +import { ensureDirectoryExists, rmIfExists } from './utils.js'; +import { kFileNotFoundError } from './constants.js'; // Generators for the Blog -const StaticGenerator = require('./generators/static'); -const HTMLGenerator = require('./generators/html'); -const RSSGenerator = require('./generators/rss'); -const TXTGenerator = require('./generators/txt'); +import StaticGenerator from './generators/static.js'; +import HTMLGenerator from './generators/html.js'; +import RSSGenerator from './generators/rss.js'; +import TXTGenerator from './generators/txt.js'; // Archiving Methods -const GemlogArchiver = require('./archivers/gemlog'); +import GemlogArchiver from './archivers/gemlog.js'; // Remote Handler -const Remote = require('./remote'); +import Remote from './remote.js'; const internals = { @@ -51,7 +49,7 @@ const internals = { * @param {Blog.tConfiguration} config the initialization options to * extend the instance */ -module.exports = class Blog { +export default class Blog { constructor(config) { @@ -112,15 +110,15 @@ module.exports = class Blog { internals.debuglog(`Publishing to ${host}`); try { - await internals.exec('which aws'); + await internals.exec('which rsync'); } catch (err) { - console.error('Please install and configure AWS CLI to publish.'); + console.error('Please install and configure rsync to publish.'); } try { - internals.debuglog(`Copying ephemeral blog from ${this.staticDirectory}`); - await internals.exec(`rsync -r ${this.staticDirectory}/ ${host}`); + internals.debuglog(`Copying ephemeral blog from ${this.blogOutputDirectory}`); + await internals.exec(`rsync -r ${this.blogOutputDirectory}/ ${host}`); } catch (err) { console.error('Failed to publish'); @@ -150,9 +148,8 @@ module.exports = class Blog { } try { - const gemlogPath = resolve(join(__dirname, '../', '.gemlog')); - internals.debuglog(`Copying archive from ${gemlogPath}`); - await internals.exec(`rsync -r ${gemlogPath}/ ${host}`); + internals.debuglog(`Copying archive from ${this.archiveOutputDirectory}`); + await internals.exec(`rsync -r ${this.archiveOutputDirectory}/ ${host}`); } catch (err) { console.error('Failed to publish archive'); @@ -171,6 +168,7 @@ module.exports = class Blog { * @instance */ async addRemote(remote) { + await ensureDirectoryExists(this.configDirectory); await Remote.add(this.remoteConfig, remote) } @@ -197,8 +195,8 @@ module.exports = class Blog { */ async syncDown() { internals.debuglog('Pulling remote state'); - await ensureDirectoryExists(this.postsDirectory); - await Remote.syncDown(this.remoteConfig, this.blogDirectory) + await ensureDirectoryExists(this.dataDirectory); + await Remote.syncDown(this.remoteConfig, this.dataDirectory) internals.debuglog('Pulled remote state'); } @@ -212,8 +210,8 @@ module.exports = class Blog { */ async syncUp() { internals.debuglog('Pushing remote state'); - await ensureDirectoryExists(this.postsDirectory); - await Remote.syncUp(this.remoteConfig, this.blogDirectory) + await ensureDirectoryExists(this.dataDirectory); + await Remote.syncUp(this.remoteConfig, this.dataDirectory) internals.debuglog('Pushed remote state'); } @@ -244,12 +242,24 @@ module.exports = class Blog { const posts = await this._readPosts(); - await StaticGenerator(this.postsDirectory, this.staticDirectory, posts); - await HTMLGenerator(this.templatesDirectory, this.staticDirectory, posts); - await RSSGenerator(this.templatesDirectory, this.staticDirectory, posts); - await TXTGenerator(this.templatesDirectory, this.staticDirectory, posts); + // Start from a clean slate. + await rmIfExists(this.blogOutputDirectory); + await ensureDirectoryExists(this.blogOutputDirectory); + + // Run each generator + await StaticGenerator(this.staticDirectory, this.blogOutputDirectory, posts); + await HTMLGenerator(await this._templateDirectoryFor('index.html'), this.blogOutputDirectory, posts); + await RSSGenerator(await this._templateDirectoryFor('feed.xml'), this.blogOutputDirectory, posts); + await TXTGenerator(await this._templateDirectoryFor('index.txt'), this.blogOutputDirectory, posts); + + // Start from a clean slate. + await rmIfExists(this.archiveOutputDirectory); + await ensureDirectoryExists(this.archiveOutputDirectory); + await ensureDirectoryExists(this.archiveDirectory); - await GemlogArchiver(this.archiveDirectory); + // Run each archiver + await GemlogArchiver(await this._templateDirectoryFor('index.gmi'), this.archiveDirectory, this.archiveOutputDirectory); + // TODO: GopherArchiver } // Reads the posts into an array @@ -410,4 +420,20 @@ module.exports = class Blog { throw new Error(internals.strings.geminiNotFound); } + + // Gets the template directory for a given template. + async _templateDirectoryFor(template) { + try { + await access(join(this.templatesDirectory, template)); + return this.templatesDirectory; + } + catch (error) { + if (error.code === kFileNotFoundError) { + internals.debuglog(`No custom template for ${template}`); + return this.defaultTemplatesDirectory; + } + + throw error; + } + } }; diff --git a/lib/constants.js b/lib/constants.js index 9b4ce31..2142718 100644 --- a/lib/constants.js +++ b/lib/constants.js @@ -1,3 +1 @@ -module.exports = { - kFileNotFoundError: 'ENOENT', -}; +export const kFileNotFoundError = 'ENOENT'; diff --git a/lib/generators/html.js b/lib/generators/html.js index 9579cb2..f7c7966 100644 --- a/lib/generators/html.js +++ b/lib/generators/html.js @@ -1,9 +1,9 @@ 'use strict'; -const { template, templateSettings } = require('dot'); -const { readFile, writeFile } = require('fs/promises'); -const { join } = require('path'); -const { debuglog } = require('util'); +import Dot from 'dot'; +import { readFile, writeFile } from 'fs/promises'; +import { join } from 'path'; +import { debuglog } from 'util'; const internals = { debuglog: debuglog('blog'), @@ -19,7 +19,7 @@ const internals = { * @param {string} target the target directory * @param {Array.} posts the list of posts */ -module.exports = async function HTMLGenerator(source, target, posts) { +export default async function HTMLGenerator(source, target, posts) { internals.debuglog('Generating HTML'); const indexTarget = join(target, internals.kIndexName); @@ -29,8 +29,8 @@ module.exports = async function HTMLGenerator(source, target, posts) { const indexTemplate = await readFile(indexLocation, { encoding: 'utf8' }); internals.debuglog('Writing HTML'); - const indexHtml = template(indexTemplate, { - ...templateSettings, + const indexHtml = Dot.template(indexTemplate, { + ...Dot.templateSettings, strip: false })({ posts }); await writeFile(indexTarget, indexHtml); diff --git a/lib/generators/rss.js b/lib/generators/rss.js index 4ae3925..a46a394 100644 --- a/lib/generators/rss.js +++ b/lib/generators/rss.js @@ -1,10 +1,10 @@ 'use strict'; -const { template, templateSettings } = require('dot'); -const { encodeXML } = require('entities'); -const { readFile, writeFile } = require('fs/promises'); -const { join } = require('path'); -const { debuglog } = require('util'); +import Dot from 'dot'; +import { encodeXML } from 'entities'; +import { readFile, writeFile } from 'fs/promises'; +import { join } from 'path'; +import { debuglog } from 'util'; const internals = { debuglog: debuglog('blog'), @@ -29,7 +29,7 @@ const internals = { * @param {string} target the target directory * @param {Array.} posts the list of posts */ -module.exports = async function RSSGenerator(source, target, posts) { +export default async function RSSGenerator(source, target, posts) { internals.debuglog('Generating RSS'); const feedTarget = join(target, internals.kFeedName); @@ -45,8 +45,8 @@ module.exports = async function RSSGenerator(source, target, posts) { title: internals.extractTitle(post.raw), html: encodeXML(post.html) })); - const feedXml = template(feedTemplate, { - ...templateSettings, + const feedXml = Dot.template(feedTemplate, { + ...Dot.templateSettings, strip: false })({ posts }); await writeFile(feedTarget, feedXml); diff --git a/lib/generators/static.js b/lib/generators/static.js index 416076c..60719e2 100644 --- a/lib/generators/static.js +++ b/lib/generators/static.js @@ -1,10 +1,10 @@ 'use strict'; -const { access, cp } = require('fs/promises'); -const { join } = require('path'); -const { debuglog } = require('util'); -const { rmIfExists } = require('../utils'); -const { kFileNotFoundError } = require('../constants'); +import { access, cp, readdir } from 'fs/promises'; +import { constants } from 'fs'; +import { join } from 'path'; +import { debuglog } from 'util'; +import { kFileNotFoundError } from '../constants.js'; const internals = { debuglog: debuglog('blog'), @@ -19,31 +19,31 @@ const internals = { * @param {string} target the target directory * @param {Array.} posts the list of posts */ -module.exports = async function StaticGenerator(source, target, posts) { +export default async function StaticGenerator(source, target, _) { - const assetsTarget = join(target, internals.kAssetsDirectoryName); + try { + await access(source, constants.R_OK); - internals.debuglog(`Removing ${assetsTarget}`); - await rmIfExists(assetsTarget); + const entries = await readdir(source, { withFileTypes: true }); + for (const entry of entries) { + const sourceAsset = join(source, entry.name); + const targetAsset = join(target, entry.name); - for (let i = 0; i < posts.length; ++i) { - const postSourcePath = join(source, `${i}`); + internals.debuglog(`Copying ${sourceAsset} to ${targetAsset}`); - try { - await access(postSourcePath); - - const assetsSource = join(postSourcePath, internals.kAssetsDirectoryName); - - internals.debuglog(`Copying ${assetsSource} to ${assetsTarget}`); - await cp(assetsSource, assetsTarget, { recursive: true }); - } - catch (error) { - if (error.code === kFileNotFoundError) { - internals.debuglog(`Skipping ${i}`); - continue; + if (entry.isDirectory()) { + await cp(sourceAsset, targetAsset, { recursive: true }); + } else { + await cp(sourceAsset, targetAsset); } - - throw error; } } + catch (error) { + if (error.code === kFileNotFoundError) { + internals.debuglog(`No static directory found in ${source}`); + return; + } + + throw error; + } }; diff --git a/lib/generators/txt.js b/lib/generators/txt.js index af5ec9c..dddcbaf 100644 --- a/lib/generators/txt.js +++ b/lib/generators/txt.js @@ -1,9 +1,9 @@ 'use strict'; -const { template, templateSettings } = require('dot'); -const { readFile, writeFile } = require('fs/promises'); -const { join } = require('path'); -const { debuglog } = require('util'); +import Dot from 'dot'; +import { readFile, writeFile } from 'fs/promises'; +import { join } from 'path'; +import { debuglog } from 'util'; const internals = { debuglog: debuglog('blog'), @@ -19,7 +19,7 @@ const internals = { * @param {string} target the target directory * @param {Array.} posts the list of posts */ -module.exports = async function TXTGenerator(source, target, posts) { +export default async function TXTGenerator(source, target, posts) { internals.debuglog('Generating TXT'); const textTarget = join(target, internals.kTextName); @@ -29,10 +29,9 @@ module.exports = async function TXTGenerator(source, target, posts) { const textTemplate = await readFile(textLocation, { encoding: 'utf8' }); internals.debuglog('Writing TXT'); - const text = template(textTemplate, { - ...templateSettings, + const text = Dot.template(textTemplate, { + ...Dot.templateSettings, strip: false })({ posts }); await writeFile(textTarget, text); }; - diff --git a/lib/remote.js b/lib/remote.js index 373958f..780ea91 100644 --- a/lib/remote.js +++ b/lib/remote.js @@ -1,16 +1,18 @@ -const { readFile, writeFile } = require('fs/promises'); -const { rmIfExists } = require('./utils'); +import { readFile, writeFile } from 'fs/promises'; +import { rmIfExists } from './utils.js'; + +import GitStrategy from './remotes/git.js'; const internals = { strings: { configurationNotFound: 'Remote configuration not set, consult help for more info.' }, strategies: [ - require('./remotes/git') + GitStrategy ] }; -module.exports = { +export default { async add(remoteConfig, remote) { await writeFile(remoteConfig, remote); diff --git a/lib/remotes/git.js b/lib/remotes/git.js index 32230ae..f6d0c07 100644 --- a/lib/remotes/git.js +++ b/lib/remotes/git.js @@ -1,12 +1,12 @@ -const { exec } = require('child_process'); -const { promisify } = require('util'); +import { exec } from 'child_process'; +import { promisify } from 'util'; const internals = { // Promisified functions exec: promisify(exec), }; -module.exports = { +export default { canHandle() { // For the future: actually check if it's a valid git url diff --git a/lib/utils.js b/lib/utils.js index 24b9407..25d204a 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -1,36 +1,34 @@ -const { access, constants, mkdir, rm } = require('fs/promises'); -const { kFileNotFoundError } = require('./constants'); +import { access, constants, mkdir, rm } from 'fs/promises'; +import { kFileNotFoundError } from './constants.js'; // File system utilities -module.exports = { - async rmIfExists(location) { +export async function rmIfExists(location) { - try { - await access(location, constants.F_OK); - await rm(location, { recursive: true }); + try { + await access(location, constants.F_OK); + await rm(location, { recursive: true }); + } + catch (error) { + if (error.code === kFileNotFoundError) { + return; } - catch (error) { - if (error.code === kFileNotFoundError) { - return; - } - throw error; - } - }, + throw error; + } +} - async ensureDirectoryExists(directory) { +export async function ensureDirectoryExists(directory) { - try { - await access(directory); + try { + await access(directory); + } + catch (error) { + if (error.code === kFileNotFoundError) { + await mkdir(directory, { recursive: true }); + return; } - catch (error) { - if (error.code === kFileNotFoundError) { - await mkdir(directory, { recursive: true }); - return; - } - throw error; - } + throw error; } -}; +} -- cgit