Skip to content

Instantly share code, notes, and snippets.

@lisp-ceo
Created July 4, 2017 10:31
Show Gist options
  • Save lisp-ceo/b0c20990e685d82fcf52814617622470 to your computer and use it in GitHub Desktop.
Save lisp-ceo/b0c20990e685d82fcf52814617622470 to your computer and use it in GitHub Desktop.
A stab at a Jekyll-based assembler for rendering Markdown in Vue.js templates
// TODO Copy over all support directories as well
// TODO Replace returning promises with async / await
// TODO Decide if it's worth making an extension to Vue.js
// TODO Decide how Markdown will interact with custom Vue.js components
// Global map of Markdown syntax to Vue.js components on a page-by-page basis with sane defaults?
// Unit testing, project setup and maintenance as an open source concern
#!/usr/bin/env node
var fs = require('fs');
var path = require('path');
let defaultInputDraftsDirectory = './drafts';
let defaultInputPostsDirectory = './posts';
let defaultOutputPostsDirectory = './static/blog';
let defaultPageSize = 20;
let MasterSummaryFileName = "blog";
let PageSummaryFileNamePrefix = "page";
let postDirectory = path.join(process.cwd(), defaultInputPostsDirectory);
let p1 = new Promise((resolve, reject) =>{
readPostsDirectory(postDirectory, resolve)
}).then((posts) => {
return new Promise(function(resolve, reject){
resolve(new PageCollection(defaultPageSize, posts))
})
}).then(pc => {
return new Promise(function(resolve, reject){
pc.writeMasterSummaryToStorage()
resolve(pc)
})
}).then(pc => {
return new Promise(function(resolve, reject){
pc.writePageSummariesToStorage()
resolve(pc)
})
}).then(pc => {
return new Promise(function(resolve, reject){
pc.writePostsToStorage()
resolve(pc)
})
}).then(pc => { console.log('Complete!')})
class Post {
constructor (body, filename) {
this.body = body;
this.filename = filename;
validateFilename(filename);
this.shortFilename = getShortFilename(filename);
this.date = this.getDateFromFilename(filename);
this.title = this.getTitleFromFilename(filename);
}
getTitleFromFilename (filename) {
var shortFilename = this.getShortFilename(filename);
return shortFilename
.split('-')
.slice(3)
.map((word, ndx, a) => (ndx == a.length -1) ? word.split('.')[0] : word)
.join(' ')
}
getDateFromFilename (filename) {
var shortFilename = this.getShortFilename(filename),
sp = shortFilename.split('-')
return new Date(sp[0], sp[1], sp[2]);
}
getShortFilename (filename) {
return filename.split('/').pop();
}
validateFilename (filename) {
var sp = this.getShortFilename(filename).split('-');
if (sp.length < 3) {
throw `Filename unable to convert file into post as filename not correct format`
}
}
isLessThan (post) {
return this.date < post.date;
}
toString () {
return `Post. Date: ${this.date}. Filename: ${this.filename}`
}
serializeObject () {
return {
title: this.title.toString(),
body: this.body.toString(),
date: this.date.toString()
}
}
get path () {
return this.shortFilename.toString()
}
}
class PageCollection {
constructor (pageCount, posts) {
this.pageCount = pageCount
this.posts = posts
this.pages = [];
this.partitionPosts();
}
partitionPosts () {
var pageCount = this.pageCount;
var posts = sortPostsByDate(this.posts);
posts.forEach((post, ndx) => {
let page = Math.floor(ndx / pageCount);
let pageNdx = ndx % 20
if(typeof(this.pages[page]) == 'undefined'){
this.pages[page] = [];
};
if(typeof(this.pages[page][pageNdx]) != 'undefined') {
throw `There\'s something already here!`;
}
this.pages[page][pageNdx] = post
});
}
toString () {
var s = `PageCollection. Pages length: ${this.pages.length}`;
this.pages.forEach(page => {
s += `\nPage. Length: ${page.length}`
page.forEach((post, pndx) => {
s += post.toString();
s += '\n';
});
});
return s
}
// TODO Handle errs with promise resolve/reject callbacks
writeMasterSummaryToStorage (cb) {
let ms = new MasterSummary(this.posts.length, this.pageCount),
mso = ms.serializeObject(),
outputPath = path.join(process.cwd(), defaultOutputPostsDirectory, ms.path);
fs.open(outputPath, 'w', (err, fd) => {
let b = new Buffer(JSON.stringify(mso));
fs.write(fd, b, (err, bytesWritten, buf) => {
if(err) { throw `Err in writing to fs: ${err}` }
cb();
});
});
}
writePageSummariesToStorage (cb) {
this.pages.forEach((page, ndx) => {
let ps = new PageSummary(page, ndx),
pso = ps.serializeObject();
outputPath = path.join(process.cwd(), defaultOutputPostsDirectory, ps.path)
fs.open(outputPath, 'w', (err, fd) => {
let b = new Buffer(JSON.stringify(pso));
fs.write(fd, b, (err, bytesWritten, buf) => {
if(err) { throw `Err in writing to fs: ${err}` }
cb();
})
})
})
}
writePostsToStorage () {
this.pages.forEach((page, ndx) => {
page.forEach((post, ndx) => {
let pso = post.serializeObject();
outputPath = path.join(process.cwd(), defaultOutputPostsDirectory, post.path)
fs.open(outputPath, 'w', (err, fd) => {
let b = new Buffer(JSON.stringify(pso));
fs.write(fd, b, (err, bytesWritten, buf) => {
if(err) { throw `Err in writing to fs: ${err}` }
cb();
})
})
})
})
}
};
class MasterSummary {
constructor (postCount, pageCount) {
this.postCount = postCount
this.pageCount = pageCount
}
serializeObject () {
return {
postCount: parseInt(this.postCount, 10),
pageCount: parseInt(this.pageCount, 10)
}
}
get path() {
return `${MasterSummaryFileName}.json`
}
}
class PageSummary {
constructor (posts, pageNumber) {
this.postSummaries = buildPostSummaries(posts);
this.pageNumber = pageNumber
}
serializeObject () {
return {
postCount: this.postCount,
postSummaries: this.postSummaries.map(ps => ps.serializeObject()),
pageNumber: parseInt(this.pageNumber, 10)
}
}
buildPostSummaries (posts) {
let postSummaries = []
posts.forEach(p => postSummaries.push(new PostSummary(p)))
return postSummaries
}
}
class PostSummary {
constructor(post, root) {
this.title = post.title
this.date = post.date
this.location = getLocationForPost(post, root);
}
getLocationForPost(post, root) {
return path.join(root, post.shortFilename);
}
serializeObject () {
return {
title: this.title.toString(),
date: this.date.toString(),
location: this.location.toString();
};
}
}
function sortPostsByDate (posts) {
return mergeSort(posts);
}
function mergeSort(posts) {
var len = posts.length
if(len < 2) { return posts; }
var mid = Math.floor(len / 2),
left = posts.slice(0, mid),
right = posts.slice(mid);
return merge(mergeSort(left), mergeSort(right));
}
function merge(left, right) {
var posts = [],
lLen = left.length,
rLen = right.length,
l = 0,
r = 0;
while(l < lLen && r < rLen) {
if(left[l].isLessThan(right[r]), 'date') {
posts.push(left[l++]);
} else {
posts.push(right[r++]);
}
}
return posts.concat(left.slice(l)).concat(right.slice(r));
}
function readPostsDirectory(postDirectory, cb) {
var posts = [];
fs.readdir(postDirectory, (err, files) => {
var fileCount = 0
if (err) {
throw `Error in attempting to open posts directory: ${postDirectory}: ${err}`;
return
}
files.forEach(file => {
let filePath = path.join(postDirectory, file);
fs.readFile(filePath, (err, data) => {
fileCount += 1
if (err) {
throw `Error attempting to read post: ${filePath}`;
return
}
try {
p = new Post(data, filePath);
posts.push(p);
} catch (excp) {
console.log(`Skipping ${filePath} as ${excp}`);
}
if (fileCount >= files.length) {
cb(posts);
}
});
});
});
}
@lisp-ceo
Copy link
Author

lisp-ceo commented Jul 4, 2017

  • Incorporate async/await syntax to cut down on promise boilerplate
  • Handle errors using Promises rather than throwing

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment