]>
git.r.bdr.sh - rbdr/blog/blob - lib/blog.js
6b3397fd7d2f008ac1de39cb4552583fd0723124
3 const { exec
} = require('child_process');
4 const { access
, mkdir
, readdir
, readFile
, rmdir
, writeFile
} = require('fs/promises');
5 const { template
} = require('dot');
6 const { ncp
} = require('ncp');
7 const { join
} = require('path');
8 const Marked
= require('marked');
9 const { debuglog
, promisify
} = require('util');
13 // Promisified functions
16 exec: promisify(exec
),
17 debuglog: debuglog('blog'),
21 kAssetsDirectoryName: 'assets',
22 kIndexName: 'index.html',
23 kFileNotFoundError: 'ENOENT',
24 kMarkdownRe: /\.md$/i,
25 kMetadataFilename: 'metadata.json',
30 markdownNotFound: 'Markdown file was not found in blog directory. Please update.'
35 * The Blog class is the blog generator, it's in charge of adding and
36 * updating posts, and handling the publishing.
39 * @param {Potluck.tConfiguration} config the initialization options to
42 module
.exports
= class Blog
{
46 Object
.assign(this, config
);
50 * Shifts the blog posts, adds the passed path to slot 0, and
55 * @param {string} postLocation the path to the directory containing
57 * @return {Promise<undefined>} empty promise, returns no value
60 async
add(postLocation
) {
62 await
this._ensurePostsDirectoryExists();
64 await
this.update(postLocation
);
68 * Adds the passed path to slot 0, and generates files.
72 * @param {string} postLocation the path to the directory containing
74 * @return {Promise<undefined>} empty promise, returns no value
77 async
update(postLocation
) {
79 const metadata
= await
this._getMetadata();
80 await
this._ensurePostsDirectoryExists();
81 await
this._copyPost(postLocation
);
82 await
this._writeMetadata(metadata
);
84 await
this._generate();
88 * Publishes the files to a static host.
92 * @return {Promise<undefined>} empty promise, returns no value
97 console
.error('Publishing not yet implemented');
98 return Promise
.resolve();
101 // Parses markdown for each page, copies assets and generates index.
105 const assetsTarget
= join(this.staticDirectory
, internals
.kAssetsDirectoryName
);
106 const indexTarget
= join(this.staticDirectory
, internals
.kIndexName
);
107 const indexLocation
= join(this.templatesDirectory
, internals
.kIndexName
);
110 internals
.debuglog(`Removing ${assetsTarget}`);
111 await
rmdir(assetsTarget
, { recursive: true });
113 for (let i
= 0; i
< this.maxPosts
; ++i
) {
114 const sourcePath
= join(this.postsDirectory
, `${i}`);
117 await
access(this.postsDirectory
);
119 const assetsSource
= join(sourcePath
, internals
.kAssetsDirectoryName
);
120 const postContentPath
= await
this._findBlogContent(sourcePath
);
122 internals
.debuglog(`Copying ${assetsSource} to ${assetsTarget}`);
123 await internals
.ncp(assetsSource
, assetsTarget
);
125 internals
.debuglog(`Reading ${postContentPath}`);
126 const postContent
= await
readFile(postContentPath
, { encoding: 'utf8' });
128 internals
.debuglog('Parsing markdown');
130 html: Marked(postContent
),
135 if (error
.code
=== internals
.kFileNotFoundError
) {
136 internals
.debuglog(`Skipping ${i}`);
144 internals
.debuglog(`Reading ${indexLocation}`);
145 const indexTemplate
= await
readFile(indexLocation
, { encoding: 'utf8' });
147 internals
.debuglog('Generating HTML');
148 const indexHtml
= template(indexTemplate
)({ posts
});
149 await
writeFile(indexTarget
, indexHtml
);
152 // Shift the posts, delete any remainder.
157 for (let i
= this.maxPosts
- 1; i
>= 0; --i
) {
158 const targetPath
= join(this.postsDirectory
, `${i}`);
159 const sourcePath
= join(this.postsDirectory
, `${i - 1}`);
162 internals
.debuglog(`Removing ${targetPath}`);
163 await
rmdir(targetPath
, { recursive: true });
165 await
access(sourcePath
); // check the source path
167 internals
.debuglog(`Shifting blog post ${sourcePath} to ${targetPath}`);
168 await internals
.ncp(sourcePath
, targetPath
);
171 if (error
.code
=== internals
.kFileNotFoundError
) {
172 internals
.debuglog(`Skipping ${sourcePath}: Does not exist.`);
181 // Attempts to read existing metadata. Otherwise generates new set.
183 async
_getMetadata() {
185 const metadataTarget
= join(this.postsDirectory
, '0', internals
.kMetadataFilename
);
188 internals
.debuglog(`Looking for metadata at ${metadataTarget}`);
189 return await
readFile(metadataTarget
);
192 internals
.debuglog(`Metadata not found or unreadable. Generating new set.`);
193 const createdOn
= Date
.now();
195 id: String(createdOn
),
199 return JSON
.stringify(metadata
, null, 2);
203 // Writes metadata. Assumes post 0 since it only gets written
206 async
_writeMetadata(metadata
) {
208 const metadataTarget
= join(this.postsDirectory
, '0', internals
.kMetadataFilename
);
209 internals
.debuglog(`Writing ${metadataTarget}`);
210 await
writeFile(metadataTarget
, metadata
);
213 // Copies a post directory to the latest slot.
215 async
_copyPost(postLocation
) {
217 const targetPath
= join(this.postsDirectory
, '0');
219 internals
.debuglog(`Removing ${targetPath}`);
220 await
rmdir(targetPath
, { recursive: true });
222 internals
.debuglog(`Adding ${postLocation} to ${targetPath}`);
223 await internals
.ncp(postLocation
, targetPath
);
226 // Ensures the posts directory exists.
228 async
_ensurePostsDirectoryExists() {
230 internals
.debuglog(`Checking if ${this.postsDirectory} exists.`);
232 await
access(this.postsDirectory
);
235 if (error
.code
=== internals
.kFileNotFoundError
) {
236 internals
.debuglog('Creating posts directory');
237 await
mkdir(this.postsDirectory
);
245 // Looks for a `.md` file in the blog directory, and returns the path
247 async
_findBlogContent(directory
) {
249 const entries
= await
readdir(directory
);
251 const markdownEntries
= entries
252 .filter((entry
) => internals
.kMarkdownRe
.test(entry
))
253 .map((entry
) => join(directory
, entry
));
255 if (markdownEntries
.length
> 0) {
256 internals
.debuglog(`Found markdown file: ${markdownEntries[0]}`);
257 return markdownEntries
[0];
260 throw new Error(internals
.strings
.markdownNotFound
);