]>
git.r.bdr.sh - rbdr/blog/blob - lib/blog.js
3 const Fs
= require('fs');
4 const Markdown
= require('markdown');
5 const Mustache
= require('mustache');
6 const Ncp
= require('ncp');
7 const Path
= require('path');
8 const Rimraf
= require('rimraf');
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}`);
115 const assetsSource
= Path
.join(sourcePath
, internals
.kAssetsDirectoryName
);
116 const postContentPath
= await
this._findBlogContent(sourcePath
);
118 internals
.debuglog(`Copying ${assetsSource} to ${assetsTarget}`);
119 await internals
.ncp(assetsSource
, assetsTarget
);
121 internals
.debuglog(`Reading ${postContentPath}`);
122 const postContent
= await internals
.fs
.readFile(postContentPath
, { encoding: 'utf8' });
124 internals
.debuglog('Parsing markdown');
126 html: Markdown
.markdown
.toHTML(postContent
),
131 internals
.debuglog(`Reading ${indexLocation}`);
132 const indexTemplate
= await internals
.fs
.readFile(indexLocation
, { encoding: 'utf8' });
134 internals
.debuglog('Generating HTML');
135 const indexHtml
= Mustache
.render(indexTemplate
, { posts
});
136 await internals
.fs
.writeFile(indexTarget
, indexHtml
);
139 // Shift the posts, delete any remainder.
143 await
this._ensurePostsDirectoryExists();
145 for (let i
= this.maxPosts
- 1; i
> 0; --i
) {
146 const targetPath
= Path
.join(this.postsDirectory
, `${i}`);
147 const sourcePath
= Path
.join(this.postsDirectory
, `${i - 1}`);
150 await internals
.fs
.access(sourcePath
);
152 internals
.debuglog(`Removing ${targetPath}`);
153 await internals
.rimraf(targetPath
);
155 internals
.debuglog(`Shifting blog post ${sourcePath} to ${targetPath}`);
156 await internals
.ncp(sourcePath
, targetPath
);
159 if (error
.code
=== internals
.kFileNotFoundError
) {
160 internals
.debuglog(`Skipping ${sourcePath}: Does not exist.`);
169 // Copies a post directory to the latest slot.
171 async
_copyPost(postLocation
) {
173 await
this._ensurePostsDirectoryExists();
175 const targetPath
= Path
.join(this.postsDirectory
, '0');
177 internals
.debuglog(`Removing ${targetPath}`);
178 await internals
.rimraf(targetPath
);
180 internals
.debuglog(`Adding ${postLocation} to ${targetPath}`);
181 await internals
.ncp(postLocation
, targetPath
);
184 // Ensures the posts directory exists.
186 async
_ensurePostsDirectoryExists() {
188 internals
.debuglog(`Checking if ${this.postsDirectory} exists.`);
190 await internals
.fs
.access(this.postsDirectory
);
193 if (error
.code
=== internals
.kFileNotFoundError
) {
194 internals
.debuglog('Creating posts directory');
195 await internals
.fs
.mkdir(this.postsDirectory
);
203 // Looks for a `.md` file in the blog directory, and returns the path
205 async
_findBlogContent(directory
) {
207 const entries
= await internals
.fs
.readdir(directory
);
209 const markdownEntries
= entries
210 .filter((entry
) => internals
.kMarkdownRe
.test(entry
))
211 .map((entry
) => Path
.join(directory
, entry
));
213 if (markdownEntries
.length
> 0) {
214 internals
.debuglog(`Found markdown file: ${markdownEntries[0]}`);
215 return markdownEntries
[0];
218 throw new Error(internals
.strings
.markdownNotFound
);