diff --git a/Cakefile b/Cakefile new file mode 100644 index 0000000..46c5f2e --- /dev/null +++ b/Cakefile @@ -0,0 +1,24 @@ +fs = require 'fs' +{spawn} = require 'child_process' + +buildlib = (callback) -> + coffee = spawn 'coffee', ['-c', '-o', 'lib/', 'src/lib/'] + coffee.stderr.on 'data', (data) -> + process.stderr.write data.toString() + coffee.stdout.on 'data', (data) -> + console.log data.toString() + coffee.on 'exit', (code) -> + callback?() if code is 0 + +buildbin = (callback) -> + coffee = spawn 'coffee', ['-c', '-o', 'bin/', 'src/bin/'] + coffee.stderr.on 'data', (data) -> + process.stderr.write data.toString() + coffee.stdout.on 'data', (data) -> + console.log data.toString() + coffee.on 'exit', (code) -> + callback?() if code is 0 + +task 'build', 'Build lib JS files from src directory', -> + buildlib() + buildbin() diff --git a/bin/cmdline.js b/bin/cmdline.js new file mode 100755 index 0000000..28b5663 --- /dev/null +++ b/bin/cmdline.js @@ -0,0 +1,3 @@ +#!/usr/bin/env node + +require('./slidewinder.js'); diff --git a/bin/slidewinder.js b/bin/slidewinder.js old mode 100755 new mode 100644 index db34a14..8947765 --- a/bin/slidewinder.js +++ b/bin/slidewinder.js @@ -1,23 +1,33 @@ -#!/usr/bin/env node -var program = require('commander') - , slidewinder = require('../lib/slidewinder.js'); - -var pjson = require('../package.json'); - -program -.version(pjson.version) -.option('-s, --slides ', - 'Comma-separated list of slides to use (no spaces)') -.option('-c, --collection ', - 'Path to slide collection') -.option('-o, --output ', - 'Path to save output (will create directory)') -.option('-t, --title ', 'Title of the deck') -.option('-a, --author <name>', 'Deck author') -.parse(process.argv); - -if (!process.argv.slice(2).length) { - program.help(); -} - -slidewinder(program); +// Generated by CoffeeScript 1.10.0 +(function() { + var fwpath, list, path, pjson, program, slidewinder; + + path = require('path'); + + program = require('commander'); + + slidewinder = require('../lib/slidewinder_lib.js'); + + pjson = require('../package.json'); + + list = function(val) { + return val.split(','); + }; + + fwpath = function(framework) { + return path.resolve(__dirname, "../extensions/frameworks", framework); + }; + + program.version(pjson.version).option('-s, --slides <slide list>', 'Comma-separated list of slides to use (no spaces)', list).option('-c, --collection <path>', 'Path to slide collection').option('-o, --output <path>', 'Path to save output (will create directory)').option('-t, --title <title>', 'Title of the deck').option('-a, --author <name>', 'Deck author').option('-f, --framework <framework>', 'HTML and JS Framework or plugin to use for slideshow generation.', fwpath).parse(process.argv); + + if (!process.argv.slice(2).length) { + program.help(); + } + + if (program.framework == null) { + program.framework = fwpath('remark'); + } + + slidewinder(program); + +}).call(this); diff --git a/extensions/frameworks/remark/helpers.js b/extensions/frameworks/remark/helpers.js new file mode 100644 index 0000000..801b8dd --- /dev/null +++ b/extensions/frameworks/remark/helpers.js @@ -0,0 +1,17 @@ + +yaml = require('js-yaml'); + +module.exports = { + slidewinder: function(context){ + slideData = context.data.root; + var bodies = slideData.slides.map(function(x) { + Object.keys(slideData.deck).forEach(function(key){ + x.attributes[key] = slideData.deck[key]; + }); + completeBody = yaml.dump(x.attributes) + '\n' + x.body + return completeBody; + }); + bodies.join('\n---\n'); + return bodies.join('\n---\n'); + } +} diff --git a/templates/remark.html b/extensions/frameworks/remark/template.html similarity index 100% rename from templates/remark.html rename to extensions/frameworks/remark/template.html diff --git a/lib/log.js b/lib/log.js index d00f5b4..34fcf58 100644 --- a/lib/log.js +++ b/lib/log.js @@ -1,49 +1,53 @@ -var winston = require('winston'); - -var levels = { - silly: 0, - input: 1, - verbose: 2, - prompt: 3, - debug: 4, - data: 5, - info: 6, - help: 7, - warn: 8, - error: 9 -}; - -var colours = { - silly: 'magenta', - input: 'grey', - verbose: 'cyan', - prompt: 'grey', - debug: 'blue', - info: 'green', - data: 'grey', - help: 'cyan', - warn: 'yellow', - error: 'red' -}; - -// create a pretty logger -var logger = function() { - - var log = new (winston.Logger)({ - transports: [new winston.transports.Console({ +// Generated by CoffeeScript 1.10.0 +(function() { + var colours, levels, logger, winston; + + winston = require('winston'); + + levels = { + silly: 0, + input: 1, + verbose: 2, + prompt: 3, + debug: 4, + data: 5, + info: 6, + help: 7, + warn: 8, + error: 9 + }; + + colours = { + silly: 'magenta', + input: 'grey', + verbose: 'cyan', + prompt: 'grey', + debug: 'blue', + info: 'green', + data: 'grey', + help: 'cyan', + warn: 'yellow', + error: 'red' + }; + + logger = function() { + var log; + log = new winston.Logger({ + transports: [ + new winston.transports.Console({ + level: 'info', + levels: levels, + colorize: true + }) + ], level: 'info', levels: levels, colorize: true - })], - level: 'info', - levels: levels, - colorize: true - }); - - winston.addColors(colours); - - return log; + }); + winston.addColors(colours); + return log; + }; -} + module.exports = logger; -module.exports = logger; +}).call(this); diff --git a/lib/slidewinder.js b/lib/slidewinder.js deleted file mode 100644 index fa03b10..0000000 --- a/lib/slidewinder.js +++ /dev/null @@ -1,121 +0,0 @@ -var fs = require('fs') - , path = require('path') - , fm = require('front-matter') - , handlebars = require('handlebars') - , _ = require('lodash') - , mkdirp = require('mkdirp') - , logger = require('./log.js') - , yaml = require('js-yaml'); - -var log_colours = { - silly: 'magenta', - input: 'grey', - verbose: 'cyan', - prompt: 'grey', - debug: 'blue', - info: 'green', - data: 'grey', - help: 'cyan', - warn: 'yellow', - error: 'red' -}; - -var slidewinder = function(data) { - - // load the slides - var collection = load_collection(data); - data.slideset = pick_slides(collection, data.slides); - - // load the template - var rel_path = '../templates/remark.html'; - var template_path = path.resolve(__dirname, rel_path); - var template = fs.readFileSync(template_path, 'utf8'); - var renderer = handlebars.compile(template); - - // register the winder - handlebars.registerHelper('slidewinder', function(all) { - - var bodies = all.data.root.slideset.map(function(x) { - return x.body; - }); - return bodies.join('\n---\n'); - - }); - - // render and save - var deck = renderer(data); - save_deck(deck, data); - -} - -// load a slide collection -var load_collection = function(data) { - - var collection = {}; - var dirpath = data.collection; - - // load each slide, parse the frontmatter, and collect - fs.readdirSync(data.collection).forEach(function(file) { - var filepath = path.resolve(dirpath, file); - var data = fs.readFileSync(filepath, 'utf8'); - var slide = fm(data); - slide.attributes.author = - // TODO: check front matter is present and parsed ok - slide.body = yaml.dump(slide.attributes) + '\n' + slide.body; - // TODO: check required metadata is present - var name = slide.attributes.name; - if (collection[name]) { - log.error('Multiple slides have the nane', name); - } else { - collection[name] = slide; - } - }); - - log.info('loaded', Object.keys(collection).length, 'slides from', dirpath); - - return collection; - -} - -// pick out slides from a collection -var pick_slides = function(collection, slidestr) { - - var slides = slidestr.split(','); - var picked = []; - - slides.forEach(function(slidename) { - var slide = collection[slidename]; - if (slide) { - picked.push(slide); - } else { - log.error('No slide found with name', slidename); - } - }); - - log.info('picked', picked.length, 'slides from collection'); - - return picked; - -} - -// save a rendered slide deck -var save_deck = function(deck, data) { - - mkdirp(data.output); - - // write deck - var deckpath = path.resolve(data.output, 'index.html'); - fs.writeFileSync(deckpath, deck); - - // write slidewinder data - var datapath = path.resolve(data.output, 'deck.json'); - fs.writeFileSync(datapath, JSON.stringify(data, null, 2)); - - var msg = 'deck (index.html) and data (deck.json) saved to'; - log.info(msg, data.output); - -} - -var log = logger(); - -module.exports = slidewinder; diff --git a/lib/slidewinder_lib.js b/lib/slidewinder_lib.js new file mode 100644 index 0000000..f11f469 --- /dev/null +++ b/lib/slidewinder_lib.js @@ -0,0 +1,127 @@ +// Generated by CoffeeScript 1.10.0 +(function() { + var PresentationFramework, _, fm, fs, handlebars, loadCollection, log, log_colours, logger, mkdirp, path, pickSlides, saveDeck, slidewinder, yaml; + + fs = require('fs'); + + path = require('path'); + + fm = require('front-matter'); + + handlebars = require('handlebars'); + + _ = require('lodash'); + + mkdirp = require('mkdirp'); + + yaml = require('js-yaml'); + + logger = require('./log.js'); + + log_colours = { + silly: 'magenta', + input: 'grey', + verbose: 'cyan', + prompt: 'grey', + debug: 'blue', + info: 'green', + data: 'grey', + help: 'cyan', + warn: 'yellow', + error: 'red' + }; + + loadCollection = function(dirpath) { + var collection, slidenum; + collection = {}; + fs.readdirSync(dirpath).forEach(function(file) { + var data, filepath, name, slide; + filepath = path.resolve(dirpath, file); + data = fs.readFileSync(filepath, 'utf8'); + slide = fm(data); + name = slide.attributes.name; + if (collection[name]) { + return log.error('Multiple slides have the name', name); + } else { + return collection[name] = slide; + } + }); + slidenum = Object.keys(collection).length; + log.info('Loaded', slidenum, 'markdown slide files from', dirpath); + return collection; + }; + + pickSlides = function(collection, selections) { + var picked; + picked = []; + selections.forEach(function(selection) { + var slide; + slide = collection[selection]; + if (slide) { + return picked.push(slide); + } else { + return log.error('No slide found with name', name); + } + }); + log.info('Picked', picked.length, 'slides from collection'); + return picked; + }; + + PresentationFramework = (function() { + function PresentationFramework(framework) { + var frameworkPath; + log.info('Looking for presentation framework module: ', framework); + frameworkPath = path.join(framework, 'template.html'); + this.template = fs.readFileSync(frameworkPath, 'utf8'); + this.renderer = handlebars.compile(this.template); + this.helpers = require(path.join(framework, 'helpers.js')); + this.renderDeck = (function(_this) { + return function(renderContext) { + var deck; + Object.keys(_this.helpers).forEach(function(key) { + return handlebars.registerHelper(key, _this.helpers[key]); + }); + deck = _this.renderer(renderContext); + return deck; + }; + })(this); + this; + } + + return PresentationFramework; + + })(); + + saveDeck = function(deck, data) { + var dataOutput, dataPath, deckPath, msg; + mkdirp(data.output); + deckPath = path.resolve(data.output, 'index.html'); + fs.writeFileSync(deckPath, deck); + dataPath = path.resolve(data.output, 'deck.json'); + dataOutput = JSON.stringify(data, null, 2); + fs.writeFileSync(dataPath, dataOutput); + msg = 'Deck (index.html) and Data (deck.json) saved to '; + return log.info(msg, data.output); + }; + + slidewinder = function(sessionData) { + var allSlides, deck, plugin, renderContext; + allSlides = loadCollection(sessionData.collection); + sessionData.slideset = pickSlides(allSlides, sessionData.slides); + plugin = new PresentationFramework(sessionData.framework); + renderContext = { + deck: { + title: sessionData.title, + author: sessionData.author + }, + slides: sessionData.slideset + }; + deck = plugin.renderDeck(renderContext); + return saveDeck(deck, sessionData); + }; + + log = logger(); + + module.exports = slidewinder; + +}).call(this); diff --git a/package.json b/package.json index 9ad4aaf..a4e6429 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,7 @@ "winston": "^1.1.0" }, "bin": { - "slidewinder": "bin/slidewinder.js" + "slidewinder": "bin/cmdline.js" }, "devDependencies": {}, "keywords": [ diff --git a/src/bin/slidewinder.coffee b/src/bin/slidewinder.coffee new file mode 100644 index 0000000..f9669cf --- /dev/null +++ b/src/bin/slidewinder.coffee @@ -0,0 +1,32 @@ +path = require 'path' +program = require 'commander' +slidewinder = require '../lib/slidewinder_lib.js' +pjson = require '../package.json' + +# Functions for co-ercing command line arguments into suitable formats. +list = (val) -> + val.split ',' + +fwpath = (framework) -> + path.resolve(__dirname, "../extensions/frameworks", framework) + +program +.version(pjson.version) +.option('-s, --slides <slide list>', + 'Comma-separated list of slides to use (no spaces)', list) +.option('-c, --collection <path>', + 'Path to slide collection') +.option('-o, --output <path>', + 'Path to save output (will create directory)') +.option('-t, --title <title>', 'Title of the deck') +.option('-a, --author <name>', 'Deck author') +.option('-f, --framework <framework>', + 'HTML and JS Framework or plugin to use for slideshow generation.', + fwpath) +.parse process.argv + +unless process.argv.slice(2).length then program.help() + +program.framework ?= fwpath 'remark' + +slidewinder program diff --git a/src/lib/log.coffee b/src/lib/log.coffee new file mode 100644 index 0000000..4414b65 --- /dev/null +++ b/src/lib/log.coffee @@ -0,0 +1,44 @@ +winston = require 'winston' + +levels = + silly: 0 + input: 1 + verbose: 2 + prompt: 3 + debug: 4 + data: 5 + info: 6 + help: 7 + warn: 8 + error: 9 + + +colours = + silly: 'magenta' + input: 'grey' + verbose: 'cyan' + prompt: 'grey' + debug: 'blue' + info: 'green' + data: 'grey' + help: 'cyan' + warn: 'yellow' + error: 'red' + + +# Create a pretty logger +logger = () -> + log = new (winston.Logger)({ + transports: [new winston.transports.Console({ + level: 'info' + levels: levels + colorize: true + })] + level: 'info' + levels: levels + colorize: true + }) + winston.addColors colours + log + +module.exports = logger diff --git a/src/lib/slidewinder_lib.coffee b/src/lib/slidewinder_lib.coffee new file mode 100644 index 0000000..558ff32 --- /dev/null +++ b/src/lib/slidewinder_lib.coffee @@ -0,0 +1,102 @@ +# Slidewinder-Lib. +fs = require 'fs' +path = require 'path' +fm = require 'front-matter' +handlebars = require 'handlebars' +_ = require 'lodash' +mkdirp = require 'mkdirp' +yaml = require 'js-yaml' +logger = require './log.js' + +log_colours = + silly: 'magenta' + input: 'grey' + verbose: 'cyan' + prompt: 'grey' + debug: 'blue' + info: 'green' + data: 'grey' + help: 'cyan' + warn: 'yellow' + error: 'red' + +# Load a Markdown format slide collection +loadCollection = (dirpath) -> + collection = {} + # Load each slide, parse the frontmatter, and collect. + fs.readdirSync(dirpath).forEach (file) -> + filepath = path.resolve(dirpath, file) + data = fs.readFileSync(filepath, 'utf8') + slide = fm data + name = slide.attributes.name + if collection[name] + log.error('Multiple slides have the name', name) + else + collection[name] = slide + slidenum = Object.keys(collection).length + log.info('Loaded', slidenum, 'markdown slide files from', dirpath) + collection + +pickSlides = (collection, selections) -> + picked = [] + selections.forEach (selection) -> + slide = collection[selection] + if slide + picked.push slide + else + log.error('No slide found with name', name) + log.info('Picked', picked.length, 'slides from collection') + picked + +class PresentationFramework + constructor: (framework) -> + log.info('Looking for presentation framework module: ', framework) + frameworkPath = path.join(framework, 'template.html') + @template = fs.readFileSync(frameworkPath, 'utf8') + @renderer = handlebars.compile @template + @helpers = require path.join(framework, 'helpers.js') + + @renderDeck = (renderContext) => + Object.keys(@helpers).forEach (key) => + handlebars.registerHelper(key, @helpers[key]) + deck = @renderer(renderContext) + deck + this + +# Save a rendered slide deck +saveDeck = (deck, data) -> + mkdirp data.output + # Write deck + deckPath = path.resolve(data.output, 'index.html') + fs.writeFileSync(deckPath, deck) + # Write slidewinder data + dataPath = path.resolve(data.output, 'deck.json') + dataOutput = JSON.stringify(data, null, 2) + fs.writeFileSync(dataPath, dataOutput) + msg = 'Deck (index.html) and Data (deck.json) saved to ' + log.info(msg, data.output) + +# Function executes the main slidewinder flow. +slidewinder = (sessionData) -> + # Load the slides, and select the ones desired. + allSlides = loadCollection sessionData.collection + sessionData.slideset = pickSlides(allSlides, sessionData.slides) + # Load the Plugin for the framework that will be used. + plugin = new PresentationFramework sessionData.framework + + # Render and save... + + # Explicitly lay out the context for the render process, rather than + # Feed the entire sessionData object in - feels safer. + renderContext = + deck: + title: sessionData.title + author: sessionData.author + slides: sessionData.slideset + + deck = plugin.renderDeck renderContext + saveDeck(deck, sessionData) + +log = logger() + +module.exports = slidewinder