Skip to content

Instantly share code, notes, and snippets.

@freekrai
Created November 3, 2015 17:09
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save freekrai/7c95a649043168b3a236 to your computer and use it in GitHub Desktop.
Save freekrai/7c95a649043168b3a236 to your computer and use it in GitHub Desktop.
export camel to jekyll

To export from camel.js to jekyll, there are a few things to do

  1. run npm install json2yaml
  2. Create a _posts folder
  3. Save export.js to your camel folder
  4. Export it: node export

This will read all files for your camel blog and export it into jekyll markdown files.

I suggest running jekyll locally first to make sure all works as expected before deploying.

var express = require('express');
var bodyParser = require('body-parser');
var compress = require('compression');
var http = require('http');
var fs = require('fs');
var qfs = require('q-io/fs');
var sugar = require('sugar');
var _ = require('underscore');
var markdownit = require('markdown-it')({
html: true,
xhtmlOut: true,
typographer: true
}).use(require('markdown-it-footnote'));
var Rss = require('rss');
var Handlebars = require('handlebars');
var YAML = require('json2yaml'), json, data, yml;
var config = require('./config');
var version = require('./package.json').version;
var Twitter = require('twitter');
// "Statics"
var postsRoot = './posts/';
var templateRoot = './templates/';
var metadataMarker = '@@';
var maxCacheSize = 50;
var postsPerPage = 10;
//var postRegex = /^(.\/)?posts\/\d{4}\/\d{1,2}\/\d{1,2}\/(\w|-)*(.md)?/;
var postRegex = /^(.\/)?posts\/\d{4}\/\d{1,2}\/\d{1,2}\/(\w|-)*(.redirect|.md)?$/;
var footnoteAnchorRegex = /[#"]fn\d+/g;
var footnoteIdRegex = /fnref\d+/g;
var utcOffset = 5;
var cacheResetTimeInMillis = 1800000;
// set your twitter information...
var twitterClient = new Twitter({
consumer_key: config.Social.autoTweets.consumer_key,
consumer_secret: config.Social.autoTweets.consumer_secret,
access_token_key: config.Social.autoTweets.access_token_key,
access_token_secret: config.Social.autoTweets.access_token_secret
});
var twitterUsername = config.Social.autoTweets.twitterUsername;
var twitterClientNeedle = config.Social.autoTweets.twitterClientNeedle;
var renderedPosts = {};
var renderedRss = {};
var renderedAlternateRss = {};
var allPostsSortedGrouped = {};
var headerSource;
var footerSource = null;
var pageHeaderTemplate = null;
var pageFooterTemplate = null;
var postHeaderTemplate = null;
var postFooterTemplate = null;
var rssFooterTemplate = null;
var singleHeaderTemplate = null;
var singleFooterTemplate = null;
var postBodyStartTemplate = null;
var postBodyEndTemplate = null;
var siteMetadata = {};
siteMetadata.SiteUrl = config.Site.Url;
siteMetadata.SiteRoot = config.Site.Url;
siteMetadata.SiteTitle = config.Site.Title;
siteMetadata.Twitter = config.Social.Twitter;
siteMetadata.siteAbout = config.Site.About;
siteMetadata.siteAuthor = config.Site.Author;
siteMetadata.DefaultImage = config.Site.DefaultImage;
siteMetadata.CurrentYear = new Date().getFullYear();
function allPostsSortedAndGrouped(completion) {
if (Object.size(allPostsSortedGrouped) !== 0) {
completion(allPostsSortedGrouped);
} else {
qfs.listTree(postsRoot, function (name, stat) {
return postRegex.test(name);
}).then(function (files) {
// Lump the posts together by day
var groupedFiles = _.groupBy(files, function (file) {
var parts = file.split('/');
return new Date(parts[1], parts[2] - 1, parts[3]);
});
// Sort the days from newest to oldest
var retVal = [];
var sortedKeys = _.sortBy(_.keys(groupedFiles), function (date) {
return new Date(date);
}).reverse();
// For each day...
_.each(sortedKeys, function (key) {
// Get all the filenames...
var articleFiles = groupedFiles[key];
var articles = [];
// ...get all the data for that file ...
_.each(articleFiles, function (file) {
if (!file.endsWith('redirect')) {
articles.push(generateHtmlAndMetadataForFile(file));
}
});
// ...so we can sort the posts...
articles = _.sortBy(articles, function (article) {
// ...by their post date and TIME.
return Date.create(article.metadata.Date);
}).reverse();
// Array of objects; each object's key is the date, value
// is an array of objects
// In that array of objects, there is a body & metadata.
// Note if this day only had a redirect, it may have no articles.
if (articles.length > 0) {
retVal.push({date: key, articles: articles});
}
});
allPostsSortedGrouped = retVal;
completion(retVal);
});
}
}
function parseMetadata(lines) {
var retVal = {};
lines.each(function (line) {
line = line.replace(metadataMarker, '');
line = line.compact();
if (line.has('=')) {
var firstIndex = line.indexOf('=');
retVal[line.first(firstIndex)] = line.from(firstIndex + 1);
}
});
// NOTE: Some metadata is added in generateHtmlAndMetadataForFile().
// Merge with site default metadata
/*
Object.merge(retVal, siteMetadata, false, function(key, targetVal, sourceVal) {
// Ensure that the file wins over the defaults.
console.log('overwriting "' + sourceVal + '" with "' + targetVal);
return targetVal;
});
*/
return retVal;
}
function performMetadataReplacements(replacements, haystack) {
_.keys(replacements).each(function (key) {
// Ensure that it's a global replacement; non-regex treatment is first-only.
haystack = haystack.replace(new RegExp(metadataMarker + key + metadataMarker, 'g'), replacements[key]);
});
return haystack;
}
// Parses the HTML and renders it.
function parseHtml(lines, replacements, postHeader, postFooter) {
// Convert from markdown
var body = performMetadataReplacements(replacements, markdownit.render(lines) );
// Perform replacements
var header = performMetadataReplacements(replacements, headerSource);
var body = body;
// Concatenate HTML
return header + '<article>' + postHeader + '<div class="entry">' + body + '</div>' + postFooter + '</article>' + footerSource;
}
// Gets all the lines in a post and separates the metadata from the body
function agetLinesFromPost(file) {
file = file.endsWith('.md') ? file : file + '.md';
var data = fs.readFileSync(file, {encoding: 'UTF8'});
// Extract the pieces
var lines = data.lines();
var metadataLines = _.filter(lines, function (line) { return line.startsWith(metadataMarker); });
var body = _.difference(lines, metadataLines).join('\n');
return {metadata: metadataLines, body: body};
}
function generateHtmlAndMetadataForLines(lines, file) {
var metadata = parseMetadata(lines.metadata);
if (typeof(file) !== 'undefined') {
metadata.relativeLink = externalFilenameForFile(file);
// If this is a post, assume a body class of 'post'.
if (postRegex.test(file)) {
metadata.BodyClass = 'post';
}
}
return {
metadata: metadata,
header: performMetadataReplacements(metadata, headerSource),
postHeader: performMetadataReplacements(metadata, postHeaderTemplate(metadata)),
rssFooter: performMetadataReplacements(metadata, rssFooterTemplate(metadata)),
unwrappedBody: performMetadataReplacements(metadata, markdownit.render(lines.body)),
html: function () {
return this.header +
this.postHeader +
this.unwrappedBody +
footerSource;
}
};
}
function externalFilenameForFile(file, request) {
var hostname = request != undefined ? request.headers.host : '';
var retVal = hostname.length ? ('http://' + hostname) : '';
retVal += file.at(0) == '/' && hostname.length > 0 ? '' : '/';
retVal += file.replace('.md', '').replace(postsRoot, '').replace(postsRoot.replace('./', ''), '');
return retVal;
}
// Gets the metadata & rendered HTML for this file
function generateHtmlAndMetadataForFile(file) {
var retVal = fetchFromCache(file);
if (retVal == undefined) {
var lines = getLinesFromPost(file);
var metadata = parseMetadata(lines.metadata);
// console.log( metadata );
metadata.relativeLink = externalFilenameForFile(file);
metadata.permalink = metadata.relativeLink;
// Description
if ( typeof(metadata.Description) === 'undefined') {
metadata.Description = metadata.Title;
}
if( metadata.permalink == '/index' ){
metadata.canonicalLink = metadata.SiteRoot;
metadata.ogtype = 'website';
}else{
metadata.canonicalLink = metadata.SiteRoot + '' + metadata.permalink;
metadata.ogtype = 'article';
}
if ( typeof(metadata.Image) === 'undefined') {
metadata.Image = metadata.DefaultImage;
}
// If this is a post, assume a body class of 'post'.
if (postRegex.test(file)) {
metadata.BodyClass = 'post';
}
if( metadata.BodyClass == 'BodyClass' ){
metadata.BodyClass = 'post';
}
var body = lines['body'];
// var html = parseHtml(body, metadata, mheader, mfooter);
addRenderedPostToCache(file, {
metadata: metadata,
body: body
});
}
return fetchFromCache(file);
}
String.prototype.capitalize = function() {
return this.charAt(0).toUpperCase() + this.slice(1);
}
function leadingZero(value){
if(value < 10){
return "0" + value.toString();
}
return value.toString();
}
function normalizedFileName(file) {
var retVal = file;
if (file.startsWith('posts')) {
retVal = './' + file;
}
retVal = retVal.replace('.md', '');
return retVal;
}
function fetchFromCache(file) {
return renderedPosts[normalizedFileName(file)] || null;
}
function addRenderedPostToCache(file, postData) {
console.log('Adding to cache: ' + normalizedFileName(file));
renderedPosts[normalizedFileName(file)] = _.extend({ file: normalizedFileName(file), date: new Date() }, postData);
if (_.size(renderedPosts) > maxCacheSize) {
var sorted = _.sortBy(renderedPosts, function (post) { return post.date; });
delete renderedPosts[sorted.first().file];
}
//console.log('Cache has ' + JSON.stringify(_.keys(renderedPosts)));
}
// Separate the metadata from the body
function getLinesFromData(data) {
// Extract the metadata
var metadataLines = _.filter(data.lines(), function (line) { return line.startsWith(metadataMarker); });
// The body starts after metadata. Thus, it starts at (index of last line of metadata + length of metadata).
var body = data.substring(data.indexOf(metadataLines[metadataLines.length - 1]) + metadataLines[metadataLines.length - 1].length).trim();
return {metadata: metadataLines, body: body};
}
// Gets all the lines in a post and separates the metadata from the body
function getLinesFromPost(file) {
file = file.endsWith('.md') ? file : file + '.md';
var data = fs.readFileSync(file, {encoding: 'UTF8'});
return getLinesFromData(data);
}
// render the code into _posts folder...
allPostsSortedAndGrouped(function (postsByDay) {
postsByDay.forEach(function (day) {
day['articles'].forEach(function (article) {
var date = Date.create( article.metadata.Date );
var d = (article.metadata.Date).split(" ");
var lastmod = d[0];
var file = (article.file).split("/");
var file = file.pop();
var file = "_posts/" + lastmod + "-" + file + ".md";
console.log( file );
var pmeta = {};
pmeta.layout = "post";
for( var i in article.metadata ){
if( i.toLowerCase() === "tags" ){
var tags = article.metadata[i];
tags = tags.split(", ");
// tags = tags.join("\n- ");
pmeta[ "tags" ] = tags;
}else if( i.toLowerCase() !== "linked" &&
i.toLowerCase() !== "relativelink" &&
i.toLowerCase() !== "permalink" &&
i.toLowerCase() !== "canonicallink" &&
i.toLowerCase() !== "description"
){
pmeta[ i.toLowerCase() ] = article.metadata[i];
}
}
yml = YAML.stringify( pmeta );
// console.log( yml );
var yml = yml.replace(/\ \ /g, "");
var body = yml + "---\n\n"+article.body;
fs.writeFile(file, body, function(err) {
if(err) {
return console.log('Unable to write file ' + err);
}
console.log( file + ': was saved');
});
});
});
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment