Created
July 4, 2017 10:31
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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); | |
} | |
}); | |
}); | |
}); | |
} |
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