slidewinder/slidewinder

View on GitHub
src/lib/slidewinder_lib.coffee

Summary

Maintainability
Test Coverage
# Slidewinder-Lib.
fs = require 'fs-extra'
et = require 'expand-tilde'
path = require 'path'
fm = require 'front-matter'
handlebars = require 'handlebars'
_ = require 'lodash'
mkdirp = require 'mkdirp'
logger = require './log.js'
yaml = require 'js-yaml'

log_colours =
  silly: 'magenta'
  input: 'grey'
  verbose: 'cyan'
  prompt: 'grey'
  debug: 'blue'
  info: 'green'
  data: 'grey'
  help: 'cyan'
  warn: 'yellow'
  error: 'red'


# A SlideCollection is a collection of slides. The collection of slides is
# Unorderd, and as such is a collection of slides which you pick and choose slides
# from by name.
class SlideCollection
  constructor: (folder, @name) ->
    @folder = path.normalize(et(folder))
    @slides = {}
    this

  parseSlides: () =>
    @slides = {}
    fs.readdirSync(@folder).forEach (file) =>
      if file.substr(-3) == '.md'
        filepath = path.resolve(@folder, file)
        data = fs.readFileSync(filepath, 'utf8')
        slide = fm data
        name = slide.attributes.name
        if @slides[name]
          log.error('Multiple slides have the name', name, 'in one collection!')
        else
          @slides[name] = slide
    log.info('Loaded', @nSlides(), 'markdown slide files from', @folder)

  slideNames: () ->
    Object.keys(@slides)

  nSlides: () ->
    @slideNames().length

  selectSlide: (name) ->
    if not @slides[name]?
      log.error('No slide exists in collection', @name, 'with a name of', name)
      process.exit()
    @slides[name]

  writeSync: (dir) ->
    @slideNames().forEach (key) =>
      slide = @slides[key]
      completeBody = "---\n#{yaml.dump(slide.attributes)}---\n#{slide.body}"
      fs.outputFileSync(path.join(et(dir), "#{key}.md"),completeBody)


# A collection of collections. Just to make picking out slides and such easier.
class CollectionManager
  constructor: (colpaths) ->
    @collections = {}
    Object.keys(colpaths).forEach (key) =>
      @addCollection(key, colpaths[key])
    this

  collectionNames: () ->
    Object.keys(@collections)

  addCollection: (name, path) ->
    @collections[name] = new SlideCollection(path, name)

  parseCollections: () ->
    @collectionNames().forEach (key) =>
      @collections[key].parseSlides()

  selectSlide: (collection, slide) ->
    if not @collections[collection]?
      log.error('No collection is loaded with a name of', collection)
      process.exit()
    @collections[collection].selectSlide(slide)

  writeAllSync: (dir) ->
    @collectionNames().forEach (key) =>
      fullpath = path.join(dir, key)
      @collections[key].writeSync(fullpath)


# Class for representing a slide deck. Unlike collections, order matters in a
# SlideDeck.
class SlideDeck
  constructor: (title, author, @collections) ->
    @globals =
      title: title
      author: author
    @rawSlides = [] # References to slides in @collections.
    @processedSlides = undefined # Copies of the @rawslides, that are manipped and processed.
    @renderedDeck = undefined

  slideNames: () ->
    names = []
    @rawSlides.forEach (slide) ->
      names.push slide.attributes.name
    names

  assemble: (selections) ->
    selections.forEach (selection) =>
      group = selection[0]
      slide = selection[1]
      @rawSlides.push(@collections.selectSlide(group, slide))


  preProcessSlides: (framework) ->
    @processedSlides = JSON.parse(JSON.stringify(@rawSlides))
    @processedSlides.forEach (slide) =>
      framework.slideProcessors.forEach (op) =>
        op(slide, @globals)

  render: (framework) ->
    renderContext =
      deck:
        title: @globals.title
        author: @globals.author
      slides: @processedSlides
    @renderedDeck = framework.renderDeck renderContext

  write: (filepath) ->
    filepath = et(filepath)
    fs.outputFileSync(path.join(filepath, 'index.html'), @renderedDeck)
    @collections.writeAllSync(path.join(filepath, 'collections'))


# Class to manage the loading and execution of steps of a presentation
# framework.
class PresentationFramework
  constructor: (framework) ->
    log.info('Looking for presentation framework module: ', framework)
    frameworkPath = path.join(__dirname, '../extensions/frameworks', framework)
    templatePath = path.join(frameworkPath, 'template.html')
    @template = fs.readFileSync(templatePath, 'utf8')
    helpersPath = path.join(frameworkPath, 'helpers.js')
    fwfuns = require helpersPath
    @renderer = handlebars.compile @template
    @slideProcessors = fwfuns['slideProcessors']
    @showHelpers = fwfuns['showHelpers']
    @renderDeck = (renderContext) =>
      Object.keys(@showHelpers).forEach (key) =>
          handlebars.registerHelper(key, @showHelpers[key])
      deck = @renderer(renderContext)
      deck
    this


exports.yamlToSpec = (filepath) ->
  inputSpecification = fs.readFileSync(filepath, 'utf8')
  specification = yaml.load(inputSpecification)
  specification.slides.forEach (slide, index) ->
    specification.slides[index] = slide.split('.')
  specification

exports.compile = (spec, outdir) ->
  log.info('Compiling slideshow...')
  log.info('Loading presentation framework...')
  plugin = new PresentationFramework spec.framework
  log.info('Loading slide collections...')
  collections = new CollectionManager spec.collections
  collections.parseCollections()
  log.info('Assembling slide deck...')
  deck = new SlideDeck(spec.title, spec.author, collections)
  deck.assemble(spec.slides)
  log.info('Pre Processing slides...')
  deck.preProcessSlides(plugin)
  log.info('Rendering slideshow...')
  deck.render(plugin)
  log.info('Writing slideshow...')
  deck.write(outdir)

log = logger()