]>
git.r.bdr.sh - rbdr/blog/blob - lib/blog.js
3 const Fs
= require('fs');
4 const Mustache
= require('mustache');
5 const Ncp
= require('ncp');
6 const Path
= require('path');
7 const Rimraf
= require('rimraf');
8 const Showdown
= require('showdown');
9 const Util
= require('util');
13 // Promisified functions
16 access: Util
.promisify(Fs
.access
),
17 mkdir: Util
.promisify(Fs
.mkdir
),
18 readdir: Util
.promisify(Fs
.readdir
),
19 readFile: Util
.promisify(Fs
.readFile
),
20 writeFile: Util
.promisify(Fs
.writeFile
)
22 ncp: Util
.promisify(Ncp
.ncp
),
23 rimraf: Util
.promisify(Rimraf
),
24 debuglog: Util
.debuglog('blog'),
28 kAssetsDirectoryName: 'assets',
29 kIndexName: 'index.html',
30 kFileNotFoundError: 'ENOENT',
31 kMarkdownRe: /\.md$/i,
36 markdownNotFound: 'Markdown file was not found in blog directory. Please update.'
41 * The Blog class is the blog generator, it's in charge of adding and
42 * updating posts, and handling the publishing.
45 * @param {Potluck.tConfiguration} config the initialization options to
48 module
.exports
= class Blog
{
52 Object
.assign(this, config
);
56 * Shifts the blog posts, adds the passed path to slot 0, and
61 * @param {string} postLocation the path to the directory containing
63 * @return {Promise<undefined>} empty promise, returns no value
66 async
add(postLocation
) {
69 await
this.update(postLocation
);
73 * Adds the passed path to slot 0, and generates files.
77 * @param {string} postLocation the path to the directory containing
79 * @return {Promise<undefined>} empty promise, returns no value
82 async
update(postLocation
) {
84 await
this._copyPost(postLocation
);
85 await
this._generate();
89 * Publishes the files to a static host.
93 * @return {Promise<undefined>} empty promise, returns no value
98 console
.error('Publishing not yet implemented');
101 // Parses markdown for each page, copies assets and generates index.
105 const assetsTarget
= Path
.join(this.staticDirectory
, internals
.kAssetsDirectoryName
);
106 const indexTarget
= Path
.join(this.staticDirectory
, internals
.kIndexName
);
107 const indexLocation
= Path
.join(this.templatesDirectory
, internals
.kIndexName
);
110 internals
.debuglog(`Removing ${assetsTarget}`);
111 await internals
.rimraf(assetsTarget
);
113 for (let i
= 0; i
< this.maxPosts
; ++i
) {
114 const sourcePath
= Path
.join(this.postsDirectory
, `${i}`);
117 await internals
.fs
.access(this.postsDirectory
);
119 const assetsSource
= Path
.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 internals
.fs
.readFile(postContentPath
, { encoding: 'utf8' });
128 internals
.debuglog('Parsing markdown');
129 const parser
= new Showdown
.Converter();
131 html: parser
.makeHtml(postContent
),
136 if (error
.code
=== internals
.kFileNotFoundError
) {
137 internals
.debuglog(`Skipping ${i}`);
145 internals
.debuglog(`Reading ${indexLocation}`);
146 const indexTemplate
= await internals
.fs
.readFile(indexLocation
, { encoding: 'utf8' });
148 internals
.debuglog('Generating HTML');
149 const indexHtml
= Mustache
.render(indexTemplate
, { posts
});
150 await internals
.fs
.writeFile(indexTarget
, indexHtml
);
153 // Shift the posts, delete any remainder.
157 await
this._ensurePostsDirectoryExists();
159 for (let i
= this.maxPosts
- 1; i
> 0; --i
) {
160 const targetPath
= Path
.join(this.postsDirectory
, `${i}`);
161 const sourcePath
= Path
.join(this.postsDirectory
, `${i - 1}`);
164 await internals
.fs
.access(sourcePath
);
166 internals
.debuglog(`Removing ${targetPath}`);
167 await internals
.rimraf(targetPath
);
169 internals
.debuglog(`Shifting blog post ${sourcePath} to ${targetPath}`);
170 await internals
.ncp(sourcePath
, targetPath
);
173 if (error
.code
=== internals
.kFileNotFoundError
) {
174 internals
.debuglog(`Skipping ${sourcePath}: Does not exist.`);
183 // Copies a post directory to the latest slot.
185 async
_copyPost(postLocation
) {
187 await
this._ensurePostsDirectoryExists();
189 const targetPath
= Path
.join(this.postsDirectory
, '0');
191 internals
.debuglog(`Removing ${targetPath}`);
192 await internals
.rimraf(targetPath
);
194 internals
.debuglog(`Adding ${postLocation} to ${targetPath}`);
195 await internals
.ncp(postLocation
, targetPath
);
198 // Ensures the posts directory exists.
200 async
_ensurePostsDirectoryExists() {
202 internals
.debuglog(`Checking if ${this.postsDirectory} exists.`);
204 await internals
.fs
.access(this.postsDirectory
);
207 if (error
.code
=== internals
.kFileNotFoundError
) {
208 internals
.debuglog('Creating posts directory');
209 await internals
.fs
.mkdir(this.postsDirectory
);
217 // Looks for a `.md` file in the blog directory, and returns the path
219 async
_findBlogContent(directory
) {
221 const entries
= await internals
.fs
.readdir(directory
);
223 const markdownEntries
= entries
224 .filter((entry
) => internals
.kMarkdownRe
.test(entry
))
225 .map((entry
) => Path
.join(directory
, entry
));
227 if (markdownEntries
.length
> 0) {
228 internals
.debuglog(`Found markdown file: ${markdownEntries[0]}`);
229 return markdownEntries
[0];
232 throw new Error(internals
.strings
.markdownNotFound
);