]>
git.r.bdr.sh - rbdr/blog/blob - lib/blog.js
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 kRemoveCommand: 'rm -rf',
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
) {
63 await
this.update(postLocation
);
67 * Adds the passed path to slot 0, and generates files.
71 * @param {string} postLocation the path to the directory containing
73 * @return {Promise<undefined>} empty promise, returns no value
76 async
update(postLocation
) {
78 await
this._copyPost(postLocation
);
79 await
this._generate();
83 * Publishes the files to a static host.
87 * @return {Promise<undefined>} empty promise, returns no value
92 console
.error('Publishing not yet implemented');
93 return Promise
.resolve();
96 // Parses markdown for each page, copies assets and generates index.
100 const assetsTarget
= join(this.staticDirectory
, internals
.kAssetsDirectoryName
);
101 const indexTarget
= join(this.staticDirectory
, internals
.kIndexName
);
102 const indexLocation
= join(this.templatesDirectory
, internals
.kIndexName
);
105 internals
.debuglog(`Removing ${assetsTarget}`);
106 await
rmdir(assetsTarget
, { recursive: true });
108 for (let i
= 0; i
< this.maxPosts
; ++i
) {
109 const sourcePath
= join(this.postsDirectory
, `${i}`);
112 await
access(this.postsDirectory
);
114 const assetsSource
= join(sourcePath
, internals
.kAssetsDirectoryName
);
115 const postContentPath
= await
this._findBlogContent(sourcePath
);
117 internals
.debuglog(`Copying ${assetsSource} to ${assetsTarget}`);
118 await internals
.ncp(assetsSource
, assetsTarget
);
120 internals
.debuglog(`Reading ${postContentPath}`);
121 const postContent
= await
readFile(postContentPath
, { encoding: 'utf8' });
123 internals
.debuglog('Parsing markdown');
125 html: Marked(postContent
),
130 if (error
.code
=== internals
.kFileNotFoundError
) {
131 internals
.debuglog(`Skipping ${i}`);
139 internals
.debuglog(`Reading ${indexLocation}`);
140 const indexTemplate
= await
readFile(indexLocation
, { encoding: 'utf8' });
142 internals
.debuglog('Generating HTML');
143 const indexHtml
= template(indexTemplate
)({ posts
});
144 await
writeFile(indexTarget
, indexHtml
);
147 // Shift the posts, delete any remainder.
151 await
this._ensurePostsDirectoryExists();
153 for (let i
= this.maxPosts
- 1; i
> 0; --i
) {
154 const targetPath
= join(this.postsDirectory
, `${i}`);
155 const sourcePath
= join(this.postsDirectory
, `${i - 1}`);
158 await
access(sourcePath
);
160 internals
.debuglog(`Removing ${targetPath}`);
161 await
rmdir(targetPath
, { recursive: true });
163 internals
.debuglog(`Shifting blog post ${sourcePath} to ${targetPath}`);
164 await internals
.ncp(sourcePath
, targetPath
);
167 if (error
.code
=== internals
.kFileNotFoundError
) {
168 internals
.debuglog(`Skipping ${sourcePath}: Does not exist.`);
177 // Copies a post directory to the latest slot.
179 async
_copyPost(postLocation
) {
181 await
this._ensurePostsDirectoryExists();
183 const targetPath
= join(this.postsDirectory
, '0');
185 internals
.debuglog(`Removing ${targetPath}`);
186 await
rmdir(targetPath
, { recursive: true });
188 internals
.debuglog(`Adding ${postLocation} to ${targetPath}`);
189 await internals
.ncp(postLocation
, targetPath
);
192 // Ensures the posts directory exists.
194 async
_ensurePostsDirectoryExists() {
196 internals
.debuglog(`Checking if ${this.postsDirectory} exists.`);
198 await
access(this.postsDirectory
);
201 if (error
.code
=== internals
.kFileNotFoundError
) {
202 internals
.debuglog('Creating posts directory');
203 await
mkdir(this.postsDirectory
);
211 // Looks for a `.md` file in the blog directory, and returns the path
213 async
_findBlogContent(directory
) {
215 const entries
= await
readdir(directory
);
217 const markdownEntries
= entries
218 .filter((entry
) => internals
.kMarkdownRe
.test(entry
))
219 .map((entry
) => join(directory
, entry
));
221 if (markdownEntries
.length
> 0) {
222 internals
.debuglog(`Found markdown file: ${markdownEntries[0]}`);
223 return markdownEntries
[0];
226 throw new Error(internals
.strings
.markdownNotFound
);