Added/updated examples

Added/updated examples

    module.exports = require 'yargs'
      .usage 'Usage: $0 [options] levelname anotherlevelname yetanotherlevelname ...'
      .example '$0',
        "Creates a level with default row and column counts,
         the default size and multiplier for each cell,
         an auto-generated name,
         and logging set to default verbosity\n"
      .example '$0 -vvvvvvvv',
        "Creates a level with default row and column counts,
         the default size and multiplier for each cell,
         an auto-generated name,
         and logging set to verbosity level 8 (max)\n"
      .example '$0 -w 20 -h 20',
        "Creates a level with default row and column counts,
         the cells 20 units wide, 20 units high,
         expanded by the default multiplier,
         an auto-generated name,
         and logging set to default verbosity\n"
      .example '$0 -w 10 -h 10 -x 4',
        "Creates a level with default row and column counts,
         40 x 40 unit cells,
         an auto-generated name,
         and logging set to default verbosity\n"
      .example '$0 lvl1 lvl2',
        "Creates two levels; named lvl1 and lvl2 -
         each with default row and column counts,
         the default size and multiplier for each cell,
         and logging set to default verbosity\n"
      .example '$0 --levels one two three',
        "Creates three levels; named one, two, and three -
         each with default row and column counts,
         the default size and multiplier for each cell,
         and logging set to default verbosity\n"
      .example '$0 -l10',
        "Creates ten auto-named levels -
         each with default row and column counts,
         the default size and multiplier for each cell,
         and logging set to default verbosity\n"
      .count 'verbosity'
      .alias 'v', 'verbosity'
      .describe 'v', 'Level of logging verbosity: 1 - 8'
      .alias 'r', 'rows'
      .describe 'r', 'Number of rows for map'
      .alias 'c', 'columns'
      .describe 'c', 'Number of columns for map'
      .alias 'w', 'width'
      .describe 'w', 'Width of a grid cell'
      .alias 'h', 'height'
      .describe 'h', 'Height of a grid cell'
      .alias 'x', 'multiplier'
      .describe 'x', 'Increase cell size by x times'
      .number 'l'
      .describe 'l', 'Number of auto-named levels to create'
      .array 'levels'
      .describe 'levels', 'Array of named levels to create'
      .array 'pipeline'
      .alias 'p', 'pipeline'
      .describe 'p', 'The steps of the render pipleline'
      .conflicts 'levels', 'l'
      .help 'help'
      .default
        verbosity:  3
        rows:       15
        columns:    50
        width:      8
        height:     7
        multiplier: 4
        pipeline:   [ 'png_blocks', 'png_walls', 'png_step_count' ]
      .epilogue '© Bryan Maynard 2016'
      .wrap 120
      .argv

    module.exports = require 'yargs'
      .usage 'Usage: $0 [options] name1 name2 ...'
      .example '$0',
        'Creates a 50 x 15 level with 16 x 14 cells and an auto-generated name'
      .example '$0 -vvvvvvvv',
        "Create a 50 x 15 level with 16 x 14 cells, an auto-generated name, and
        logging set to verbosity level 8 (max)"
      .example '$0 -w 20 -h 20',
        'Creates a 50 x 15 level with 40 X 40 cells & an auto-generated name'
      .example '$0 -w 10 -h 10 -x 4',
        'Creates a 50 x 15 level with 40 x 40 cells and an auto-generated name'
      .example '$0 lvl1 lvl2',
        'Creates two levels, lvl1 & lvl2, each 50 X 15 with 16 x 14 cells'
      .count 'verbosity'
      .alias 'v', 'verbosity'
      .describe 'v', 'Level of logging verbosity: 1 - 8'
      .alias 'r', 'rows'
      .describe 'r', 'Number of rows for map'
      .alias 'c', 'columns'
      .describe 'c', 'Number of columns for map'
      .alias 'w', 'width'
      .describe 'w', 'Width of a grid cell'
      .alias 'h', 'height'
      .describe 'h', 'Height of a grid cell'
      .alias 'x', 'multiplier'
      .describe 'x', 'Increase cell size by x times'
      .number 'l'
      .describe 'l', 'Number of auto-named levels to create'
      .array 'levels'
      .describe 'levels', 'Array of named levels to create'
      .array 'pipeline'
      .alias 'p', 'pipeline'
      .describe 'p', 'The steps of the render pipleline'
      .conflicts 'levels', 'l'
      .help 'help'
      .default
        verbosity:  3
        rows:       15
        columns:    50
        width:      8
        height:     7
        multiplier: 4
        pipeline:   [ 'png_blocks', 'png_walls', 'png_step_count' ]
      .epilogue '© Bryan Maynard 2016'
      .wrap 120
      .argv


Clean

Clean

# Grunt build process - Dynamic documentation

    {exec} = require 'child_process'
    fs = require 'fs'
    path = require 'path'
    {Transform} = require 'stream'

    _ = require 'lodash'
    {LineStream} = require 'byline'
    glob = require 'grunt/node_modules/glob'
    Promise = require 'bluebird'

Implement a Transform stream to accumulate hook invocations from a source file.

    class HookInvocations extends Transform

      constructor: ->
        super

        @list = []

      _transform: (chunk, encoding, done) ->
        line = chunk.toString('utf8')
        if matches = line.match /^\#\#\#\# Invoke hook `([^`]+)`/
          @list.push matches[1]

        done()

Implement a Transform stream to accumulate hook implementations from a source
file.

    class HookImplementations extends Transform

      constructor: ->
        super

        @list = []

      _transform: (chunk, encoding, done) ->
        line = chunk.toString('utf8')
        if matches = line.match /^\#\#\#\# Implements hook `([^`]+)`/
          @list.push matches[1]

        done()

Implement a Transform stream fo accumulate TODOs from a source file. Also
caches lines to be able to build context around each TODO item.

    class Todos extends Transform

      @context = 4

      constructor: ->
        super

        @lines = []
        @todos = []

      _transform: (chunk, encoding, done) ->
        @lines.push line = chunk.toString('utf8')
        if matches = line.match /^\#\#\#\#\#\# TODO/
          @todos.push @lines.length - 1

        done()

      withContext: ->

        for todo in @todos

          start = Math.max 0, todo - Todos.context
          end = Math.min @lines.length - 1, todo + Todos.context

          index: todo
          lines: @lines.slice start, end

Gather all source files.

    _allSourceFiles = ->
      new Promise (resolve, reject) ->
        glob(
          '{{client,custom,packages,server}/**/*.litcoffee,*.litcoffee}'
          (error, files) ->
            return reject error if error?
            resolve files
        )

Generate an HTML ID from a string.

    _idFromString = (string) -> string.replace(
      /[/'']/g, ''
    ).replace(
      /\[(.*)\]\(.*\)/g, '$1'
    ).replace(
      /[^0-9A-Za-z-]+/g, '-'
    ).replace(
      /\-+/g, '-'
    ).toLowerCase()

Get the source path from a filename. This removes the extension and any /index
portion from the end of the filename.

    _sourcePath = (filename) ->

        dirname = path.dirname filename
        if dirname is '.' then dirname = '' else dirname += '/'
        extname = path.extname filename
        filename = "#{dirname}#{path.basename filename, extname}"

        parts = filename.split '/'
        parts.pop() if parts[parts.length - 1] is 'index'
        return parts.join '/'

Collate a list of files by type (client or server).

    _collateFilesByType = (files) ->

      client = []
      server = []

      for file in files
        parts = file.split '/'

        if parts[0] is 'client'
          client.push file
        else if parts[0] is 'custom' and parts[2] is 'client'
          client.push file
        else if parts[0] is 'packages' and parts[2] is 'client'
          client.push file
        else
          server.push file

      client: client, server: server

Gather statistics for all files.

    fileStatsListPromise = _allSourceFiles().then (allFiles) ->

      allFilesPromises = for type, files of _collateFilesByType allFiles

        typePromises = for file in files

          do (type, file) -> new Promise (resolve, reject) ->

            fstream = fs.createReadStream file
            fstream.pipe lineStream = new LineStream keepEmptyLines: true

Pass all files through the Transform list to parse out relevant information.

            lineStream.pipe hookImplementations = new HookImplementations()
            lineStream.pipe hookInvocations = new HookInvocations()
            lineStream.pipe todos = new Todos()

            fstream.on 'error', reject

            fstream.on 'end', ->

Include all information from Transform streams in the statistics.

              resolve(
                type: type
                file: file
                implementations: hookImplementations.list
                invocations: hookInvocations.list
                todos: todos.withContext()
              )

        Promise.all typePromises

      Promise.all(allFilesPromises).then (fileStatsLists) ->
        _.flatten fileStatsLists

Massage the statistics to help rendering the hooks page.

    fileStatsListPromise.then((fileStatsList) ->

      hooksIndex = {}

      indexes = {}
      keys = ['implementations', 'invocations']

      for fileStats in fileStatsList

        parts = fileStats.file.split '/'
        for remove in ['client', 'packages']
          if ~(index = parts.indexOf remove)
            parts.splice index, 1
        mergeFile = parts.join '/'

        for key in keys
          indexes[key] ?= {}

          for hook in fileStats[key]
            indexes[key][hook] ?= {}

            indexes[key][hook][mergeFile] ?= fullName: fileStats.file
            (indexes[key][hook][mergeFile].types ?= []).push fileStats.type

            hooksIndex[hook] = true

      for key in keys
        for hook, stats of indexes[key]
          for file of stats
            stats[file].types = _.unique stats[file].types ? []

      hooks = (hook for hook of hooksIndex).sort()

      hookFiles = {}
      for hook in hooks
        hookFiles[hook] = try
          fs.readFileSync "docs/hook/#{hook}.md", 'utf8'
        catch error
          ''

      O =
        hookFiles: hookFiles
        hooks: hooks

      O[key] = indexes[key] for key in keys

      return O

Render the hooks page.

    ).then((O) ->

      {hookFiles, hooks} = O

      keys = ['implementation', 'invocation']

      wordingFor =
        implementation: 'implements'
        invocation: 'invoke'

      render = fs.readFileSync 'docs/hooks.template.md', 'utf8'

      for hook in hooks

        render += "## #{hook}\n\n"
        render += hookFiles[hook] + '\n\n' if hookFiles[hook]

        for key in keys
          pluralKey = "#{key}s"

          if O[pluralKey][hook]?

            count = 0
            for file, {types} of O[pluralKey][hook]
              count += types.length

            render += '
'
            render += "
#{count} #{key}"
            render += 's' if count > 1
            render += '
\n'
            render += '
\n'

            stripe = 0
            instances = for file, {fullName, types} of O[pluralKey][hook]

Remove packages and file part.

              parts = fullName.split '/'
              parts.shift()
              parts.pop()
              packageName = parts.join '/'

              types = types.map (type) ->
                "
#{_sourcePath file} (#{type})	#{key}
"
              types.join ''

            render += instances.join ''

            render += '
\n'
            render += '
'
            render += '\n\n'

      new Promise (resolve, reject) ->
        fs.writeFile 'docs/hooks.md', render, (error) ->
          return reject error if error?
          resolve()

    ).done()

Render the TODOs page.

    fileStatsListPromise.then((fileStatsList) ->

      render = fs.readFileSync 'docs/todos.template.md', 'utf8'

      for fileStats in fileStatsList

Keep track of used IDs, it will be necessary to link to the correct location
hash in the case of multiple TODO items with the same wording.

        idMap = {}

        for todo in fileStats.todos

          render += '\n'

          id = ''
          for line, index in todo.lines

If this is the line with the TODO, parse the ID from the TODO item text, and
render it as h2 (TODO are h6) to increase visibility.

            if index is Todos.context
              id = _idFromString(line).slice 1, -1

              render += line.slice 4
            else
              render += '    '
              render += " #{line}"
            render += '\n'

          render += '\n'

          filename = _sourcePath fileStats.file

Keep track of ID usage and modify the location hash for subsequent uses.

          if idMap[id]?
            idMap[id] += 1
            id += "_#{idMap[id]}"
          else
            idMap[id] = 0

          render += "[the above found in #{fileStats.file}:#{todo.index}](source/#{filename}##{id})\n"

      new Promise (resolve, reject) ->
        fs.writeFile 'docs/todos.md', render, (error) ->
          return reject error if error?
          resolve()

    ).done()

Add all the source files to a generated mkdocs.yml

    _allSourceFiles().then (files) ->

      yml = fs.readFileSync 'docs/mkdocs.template.yml', 'utf8'

      for file in files
        parts = file.split '/'

        for i in [0...parts.length]
          try
            fs.mkdirSync "docs/source/#{parts.slice(0, i).join '/'}"
          catch error

        fs.createReadStream(file).pipe(
          fs.createWriteStream("docs/source/#{file}")
        )

Add them under the 'Source code' path.

        yml += "- [source/#{file}, 'Source code', '#{file}']\n"

      fs.writeFileSync 'mkdocs.yml', yml

      return

Render the packages page.

    fileStatsListPromise.then((fileStatsList) ->

Sort by package name first.

      newList = []

      for fileStats in fileStatsList

        parts = fileStats.file.split '/'
        continue unless ~['custom', 'packages'].indexOf parts[0]

        sourcePath = _sourcePath fileStats.file

        parts = sourcePath.split '/'
        pkg = parts.join '/'
        fileStats.pkg = pkg.split('/').slice(1).join '/'

        newList.push fileStats

      newList.sort (l, r) ->
        return -1 if l.type is 'client' and r.type is 'server'
        return 1 if l.type is 'server' and r.type is 'client'
        if l.pkg < r.pkg then -1 else if l.pkg > r.pkg then 1 else 0

    ).then((fileStatsList) ->

      render = fs.readFileSync 'docs/packages.template.md', 'utf8'
      render += '\n'

      type = null

      for fileStats in fileStatsList

        if fileStats.type isnt type
          type = fileStats.type

          render += if type is 'client'
            '## Client-side'
          else
            '## Server-side'

          render += '\n\n'

        pkgParts = fileStats.pkg.split '/'
        isSubpackage = pkgParts.length isnt 1 and pkgParts.pop() isnt 'client'

Link to the package.

        sourcePath = _sourcePath fileStats.file

        if isSubpackage
          render += '> '
          parts = fileStats.pkg.split '/'
          parentPkg = parts.shift()
          subPkg = parts.join '/'
          render += "## [#{parentPkg}/#{subPkg}](source/#{sourcePath})"
        else
          render += "## [#{fileStats.pkg}](source/#{sourcePath})"

Naively parse out the file description. It must be wrapped in asterisks, i.e.
italicized in markdown.

###### TODO: This 'chunk' parsing should be done with a Transform like the others.

        data = fs.readFileSync fileStats.file, 'utf8'
        chunks = data.split '\n\n'

        title = chunks[0]
        if 35 is title.charCodeAt 0
          render += '\n\n'
          render += '> ' if isSubpackage
          render += "#{title.substr 2}"

        render += '\n\n'

        description = chunks[1]
        if 42 is description.charCodeAt 0 and 42 is description.charCodeAt description.length - 1
          render += '> ' if isSubpackage
          render += "#{description}\n\n"

        if fileStats.implementations.length > 0
          render += '> ' if isSubpackage
          render += '
'
          render += '
Implements hooks
'
          render += '
\n'
          render += fileStats.implementations.map((hook, index) ->
            "
#{hook}	implementation
\n"
          ).join ''
          render += '
\n'
          render += '
'
          render += '\n\n'

        if fileStats.invocations.length > 0
          render += '> ' if isSubpackage
          render += '
'
          render += '
Implements hooks
'
          render += '
\n'
          render += fileStats.implementations.map((hook, index) ->
            "
#{hook}	invocation
\n"
          ).join ''
          render += '
\n'
          render += '
'
          render += '\n\n'

###### TODO: We should do some preprocessing hre with a transform, namely linking the hook headers to their respective documentation.

      fs.writeFileSync 'docs/packages.md', render

    )

# Grunt build process - Dynamic documentation

    {exec} = require 'child_process'
    fs = require 'fs'
    path = require 'path'
    {Transform} = require 'stream'

    _ = require 'lodash'
    {LineStream} = require 'byline'
    glob = require 'grunt/node_modules/glob'
    Promise = require 'bluebird'

Implement a Transform stream to accumulate hook invocations from a source file.

    class HookInvocations extends Transform

      constructor: ->
        super

        @list = []

      _transform: (chunk, encoding, done) ->
        line = chunk.toString('utf8')
        if matches = line.match /^\#\#\#\# Invoke hook `([^`]+)`/
          @list.push matches[1]

        done()

Implement a Transform stream to accumulate hook implementations from a source
file.

    class HookImplementations extends Transform

      constructor: ->
        super

        @list = []

      _transform: (chunk, encoding, done) ->
        line = chunk.toString('utf8')
        if matches = line.match /^\#\#\#\# Implements hook `([^`]+)`/
          @list.push matches[1]

        done()

Implement a Transform stream fo accumulate TODOs from a source file. Also
caches lines to be able to build context around each TODO item.

    class Todos extends Transform

      @context = 4

      constructor: ->
        super

        @lines = []
        @todos = []

      _transform: (chunk, encoding, done) ->
        @lines.push line = chunk.toString('utf8')
        if matches = line.match /^\#\#\#\#\#\# TODO/
          @todos.push @lines.length - 1

        done()

      withContext: ->

        for todo in @todos

          start = Math.max 0, todo - Todos.context
          end = Math.min @lines.length - 1, todo + Todos.context

          index: todo
          lines: @lines.slice start, end

Gather all source files.

    _allSourceFiles = ->
      new Promise (resolve, reject) ->
        glob(
          '{{client,custom,packages,server}/**/*.litcoffee,*.litcoffee}'
          (error, files) ->
            return reject error if error?
            resolve files
        )

Generate an HTML ID from a string.

    _idFromString = (string) -> string.replace(
      /[/'']/g, ''
    ).replace(
      /\[(.*)\]\(.*\)/g, '$1'
    ).replace(
      /[^0-9A-Za-z-]+/g, '-'
    ).replace(
      /\-+/g, '-'
    ).toLowerCase()

Get the source path from a filename. This removes the extension and any /index
portion from the end of the filename.

    _sourcePath = (filename) ->

        dirname = path.dirname filename
        if dirname is '.' then dirname = '' else dirname += '/'
        extname = path.extname filename
        filename = "#{dirname}#{path.basename filename, extname}"

        parts = filename.split '/'
        parts.pop() if parts[parts.length - 1] is 'index'
        return parts.join '/'

Collate a list of files by type (client or server).

    _collateFilesByType = (files) ->

      client = []
      server = []

      for file in files
        parts = file.split '/'

        if parts[0] is 'client'
          client.push file
        else if parts[0] is 'custom' and parts[2] is 'client'
          client.push file
        else if parts[0] is 'packages' and parts[2] is 'client'
          client.push file
        else
          server.push file

      client: client, server: server

Gather statistics for all files.

    fileStatsListPromise = _allSourceFiles().then (allFiles) ->

      allFilesPromises = for type, files of _collateFilesByType allFiles

        typePromises = for file in files

          do (type, file) -> new Promise (resolve, reject) ->

            fstream = fs.createReadStream file
            fstream.pipe lineStream = new LineStream keepEmptyLines: true

Pass all files through the Transform list to parse out relevant information.

            lineStream.pipe hookImplementations = new HookImplementations()
            lineStream.pipe hookInvocations = new HookInvocations()
            lineStream.pipe todos = new Todos()

            fstream.on 'error', reject

            fstream.on 'end', ->

Include all information from Transform streams in the statistics.

              resolve(
                type: type
                file: file
                implementations: hookImplementations.list
                invocations: hookInvocations.list
                todos: todos.withContext()
              )

        Promise.all typePromises

      Promise.all(allFilesPromises).then (fileStatsLists) ->
        _.flatten fileStatsLists

Massage the statistics to help rendering the hooks page.

    fileStatsListPromise.then((fileStatsList) ->

      hooksIndex = {}

      indexes = {}
      keys = ['implementations', 'invocations']

      for fileStats in fileStatsList

        parts = fileStats.file.split '/'
        for remove in ['client', 'packages']
          if ~(index = parts.indexOf remove)
            parts.splice index, 1
        mergeFile = parts.join '/'

        for key in keys
          indexes[key] ?= {}

          for hook in fileStats[key]
            indexes[key][hook] ?= {}

            indexes[key][hook][mergeFile] ?= fullName: fileStats.file
            (indexes[key][hook][mergeFile].types ?= []).push fileStats.type

            hooksIndex[hook] = true

      for key in keys
        for hook, stats of indexes[key]
          for file of stats
            stats[file].types = _.unique stats[file].types ? []

      hooks = (hook for hook of hooksIndex).sort()

      hookFiles = {}
      for hook in hooks
        hookFiles[hook] = try
          fs.readFileSync "docs/hook/#{hook}.md", 'utf8'
        catch error
          ''

      O =
        hookFiles: hookFiles
        hooks: hooks

      O[key] = indexes[key] for key in keys

      return O

Render the hooks page.

    ).then((O) ->

      {hookFiles, hooks} = O

      keys = ['implementation', 'invocation']

      wordingFor =
        implementation: 'implements'
        invocation: 'invoke'

      render = fs.readFileSync 'docs/hooks.template.md', 'utf8'

      for hook in hooks

        render += "## #{hook}\n\n"
        render += hookFiles[hook] + '\n\n' if hookFiles[hook]

        for key in keys
          pluralKey = "#{key}s"

          if O[pluralKey][hook]?

            count = 0
            for file, {types} of O[pluralKey][hook]
              count += types.length

            render += '
'
            render += "
#{count} #{key}"
            render += 's' if count > 1
            render += '
\n'
            render += '
\n'

            # render += "!!! note \"#{count} #{key}"
            # render += 's' if count > 1
            # render += '"\n'

            stripe = 0
            instances = for file, {fullName, types} of O[pluralKey][hook]

Remove packages and file part.

              parts = fullName.split '/'
              parts.shift()
              parts.pop()
              packageName = parts.join '/'

              types = types.map (type) ->
                "
#{_sourcePath file} (#{type})	#{key}
"
              types.join ''

            render += instances.join ''

            render += '
\n'
            render += '
'
            render += '\n\n'

            render += '\n\n'

      new Promise (resolve, reject) ->
        fs.writeFile 'docs/hooks.md', render, (error) ->
          return reject error if error?
          resolve()

    ).done()

Render the TODOs page.

    fileStatsListPromise.then((fileStatsList) ->

      render = fs.readFileSync 'docs/todos.template.md', 'utf8'

      for fileStats in fileStatsList

Keep track of used IDs, it will be necessary to link to the correct location
hash in the case of multiple TODO items with the same wording.

        idMap = {}

        for todo in fileStats.todos

          render += '\n'

          id = ''
          for line, index in todo.lines

If this is the line with the TODO, parse the ID from the TODO item text, and
render it as h2 (TODO are h6) to increase visibility.

            if index is Todos.context
              id = _idFromString(line).slice 1, -1

              render += line.slice 4
            else
              render += '    '
              render += " #{line}"
            render += '\n'

          render += '\n'

          filename = _sourcePath fileStats.file

Keep track of ID usage and modify the location hash for subsequent uses.

          if idMap[id]?
            idMap[id] += 1
            id += "_#{idMap[id]}"
          else
            idMap[id] = 0

          render += "[the above found in #{fileStats.file}:#{todo.index}](source/#{filename}##{id})\n"

      new Promise (resolve, reject) ->
        fs.writeFile 'docs/todos.md', render, (error) ->
          return reject error if error?
          resolve()

    ).done()

Add all the source files to a generated mkdocs.yml

    _allSourceFiles().then (files) ->

      yml = fs.readFileSync 'docs/mkdocs.template.yml', 'utf8'

      for file in files
        parts = file.split '/'

        for i in [0...parts.length]
          try
            fs.mkdirSync "docs/source/#{parts.slice(0, i).join '/'}"
          catch error

        fs.createReadStream(file).pipe(
          fs.createWriteStream("docs/source/#{file}")
        )

Add them under the 'Source code' path.

        yml += "- [source/#{file}, 'Source code', '#{file}']\n"

      fs.writeFileSync 'mkdocs.yml', yml

      return

Render the packages page.

    fileStatsListPromise.then((fileStatsList) ->

Sort by package name first.

      newList = []

      for fileStats in fileStatsList

        parts = fileStats.file.split '/'
        continue unless ~['custom', 'packages'].indexOf parts[0]

        sourcePath = _sourcePath fileStats.file

        parts = sourcePath.split '/'
        pkg = parts.join '/'
        fileStats.pkg = pkg.split('/').slice(1).join '/'

        newList.push fileStats

      newList.sort (l, r) ->
        return -1 if l.type is 'client' and r.type is 'server'
        return 1 if l.type is 'server' and r.type is 'client'
        if l.pkg < r.pkg then -1 else if l.pkg > r.pkg then 1 else 0

    ).then((fileStatsList) ->

      render = fs.readFileSync 'docs/packages.template.md', 'utf8'
      render += '\n'

      type = null

      for fileStats in fileStatsList

        if fileStats.type isnt type
          type = fileStats.type

          render += if type is 'client'
            '## Client-side'
          else
            '## Server-side'

          render += '\n\n'

        pkgParts = fileStats.pkg.split '/'
        isSubpackage = pkgParts.length isnt 1 and pkgParts.pop() isnt 'client'

Link to the package.

        sourcePath = _sourcePath fileStats.file

        if isSubpackage
          render += '> '
          parts = fileStats.pkg.split '/'
          parentPkg = parts.shift()
          subPkg = parts.join '/'
          render += "## [#{parentPkg}/#{subPkg}](source/#{sourcePath})"
        else
          render += "## [#{fileStats.pkg}](source/#{sourcePath})"

Naively parse out the file description. It must be wrapped in asterisks, i.e.
italicized in markdown.

###### TODO: This 'chunk' parsing should be done with a Transform like the others.

        data = fs.readFileSync fileStats.file, 'utf8'
        chunks = data.split '\n\n'

        title = chunks[0]
        if 35 is title.charCodeAt 0
          render += '\n\n'
          render += '> ' if isSubpackage
          render += "#{title.substr 2}"

        render += '\n\n'

        description = chunks[1]
        if 42 is description.charCodeAt 0 and 42 is description.charCodeAt description.length - 1
          render += '> ' if isSubpackage
          render += "#{description}\n\n"

        if fileStats.implementations.length > 0
          render += '> ' if isSubpackage
          render += '
'
          render += '
Implements hooks
'
          render += '
\n'
          render += fileStats.implementations.map((hook, index) ->
            "
#{hook}	implementation
\n"
          ).join ''
          render += '
\n'
          render += '
'
          render += '\n\n'

        if fileStats.invocations.length > 0
          render += '> ' if isSubpackage
          render += '
'
          render += '
Implements hooks
'
          render += '
\n'
          render += fileStats.implementations.map((hook, index) ->
            "
#{hook}	invocation
\n"
          ).join ''
          render += '
\n'
          render += '
'
          render += '\n\n'

###### TODO: We should do some preprocessing hre with a transform, namely linking the hook headers to their respective documentation.

      fs.writeFileSync 'docs/packages.md', render

    )


Fix name clash

Fix name clash


Report errors on madul hydration

Report errors on madul hydration


fixes

fixes

# /api/Extras/Batman.I18N

`Batman.I18N` is a batman.js extra ([available in CoffeeScript](https://github.com/batmanjs/batman/blob/master/src/extras/batman.i18n.coffee)) for providing multi-langauge support in your application. It is modeled after [Rails i18n](http://guides.rubyonrails.org/i18n.html).

To use `Batman.I18N`, you must:

- Include the [batman.js extra](https://github.com/batmanjs/batman/blob/master/src/extras/batman.i18n.coffee) in your project
- Enable `Batman.I18N` with `Batman.I18N.enable()`
- Define locales on the client or [provide support from the server](#getting_locales_from_the_server)

For example:

```coffeescript
# after including Batman.I18N
Batman.I18N.enable()

# define locales:
Batman.I18N.set 'locales.en',
  messages:
    welcome: "Welcome"
  account:
    sign_in: "Sign In"
    register: "Register"

Batman.I18N.set 'locales.zh'
  messages:
    welcome: '欢迎'

# get translations:
Batman.I18N.get("locale")    # "en", default
Batman.t("messages.welcome") # "Welcome"
Batman.I18N.set("locale", "zh")
Batman.t("messages.welcome") # "欢迎"
```

You can get translations for the current locale by using `t` in view bindings:

```html
<p data-source='t.messages.welcome'></p>
```

## Getting locales from the server

`Batman.I18N.get('locales')` returns a `Batman.I18N.LocalesStorage`. This object's default accessor fetches locale objects from the server and caches them on the client. For example:

```coffeescript
Batman.I18N.get('locales.es')     # GET /locales/es.json
Batman.I18N.get('locales.fr')     # GET /locales/fr.json
```

`Batman.I18N.translate` will fetch a locale if it isn't loaded already:

```
Batman.I18N.set('locale', 'zh')
Batamn.I18N.translate('welcome')  # GET /locales/zh.json
```

The `Batman.Request` expects a JSON response with the locale object:

```coffeescript
{
  "zh" : {
    "welcome" : "欢迎",
    "account" : {
      "sign_in" : "登录",
      "register" : "注册",
    }
  }
}
```

When the new values are returned, view bindings will be automatically updated.

## Error messages

You can provide error messages for a new locale by including translations in the same structure as batman.js's default messages. [See the batman.js source](https://github.com/batmanjs/batman/blob/master/src/model/validations/validators.coffee) for the structure of these messages.

## @.defaultLocale[= "en"] : String

The default locale for `Batman.I18N`. Override this to make your app start in a locale other than `"en"`.

## @%locale : String

The current locale for `Batman.I18N`, used by `Batman.translate`. Set this value to change your locale:

```coffeescript
Batman.I18N.set('locale', 'fr') # Bienvenue!
```

## @%locales : I18N.LocalesStorage

Returns the current `Batman.I18N.LocalesStorage`, which stores locales and there translations. You can set keys on `locales` to define translations:

```coffeescript
Batman.I18N.set("locales.uz", { ... })
```

See ["Getting locales from the server"](#getting_locales_from_the_server) for information about how `Batman.I18N.LocalesStorage` handles missing keys.

## @%translations : Object

Returns the defined translations for the current locale.

## @enable()

Turns on `Batman.I18N` by:

- Storing batman.js's default error messages to the `"en"` locale
- Overriding `Batman.translate` to use `Batman.I18N.translate`

## @disable()

Turns off `Batman.I18N` by restoring previous functionality to `Batman.translate`.

# /api/Extras/Batman.I18N

`Batman.I18N` is a batman.js extra ([available in CoffeeScript](https://github.com/batmanjs/batman/blob/master/src/extras/batman.i18n.coffee)) for providing multi-langauge support in your application. It is modeled after [Rails i18n](http://guides.rubyonrails.org/i18n.html).

To use `Batman.I18N`, you must:

- Include the [batman.js extra](https://github.com/batmanjs/batman/blob/master/src/extras/batman.i18n.coffee) in your project
- Enable `Batman.I18N` with `Batman.I18N.enable()`
- Define locales on the client or [provide support from the server](/docs/api/batman.i18n.html#getting_locales_from_the_server)

For example:

```coffeescript
# after inlcuding Batman.I18N
Batman.I18N.enable()
Batman.I18N.set 'locales.en',
  messages:
    welcome: "Welcome"
  account:
    sign_in: "Sign In"
    register: "Register"

Batman.I18N.get("locale")    # "en", default
Batman.t("messages.welcome") # "Welcome"
Batman.I18N.set("locale", "zh")
Batman.t("messages.welcome") # "欢迎"
```

You can get translations for the current locale by using `t` in view bindings:

```html
<p data-source='t.messages.welcome'></p>
```

## Getting locales from the server

`Batman.I18N.get('locales')` returns a `Batman.I18N.LocalesStorage`. This object's default accessor fetches locale objects from the server and caches them on the client. For example:

```coffeescript
Batman.I18N.get('locales.es')     # GET /locales/es.json
Batman.I18N.get('locales.fr')     # GET /locales/fr.json
```

`Batman.I18N.translate` will fetch a locale if it isn't loaded already:

```
Batman.I18N.set('locale', 'zh')
Batamn.I18N.translate('welcome')  # GET /locales/zh.json
```

The `Batman.Request` expects a JSON response with the locale object:

```coffeescript
{
  "zh" : {
    "welcome" : "欢迎",
    "account" : {
      "sign_in" : "登录",
      "register" : "注册",
    }
  }
}
```

When the new values are returned, view bindings will be automatically updated.

## Error messages

You can provide error messages for a new locale by including translations in the same structure as batman.js's default messages. [See the batman.js source](https://github.com/batmanjs/batman/blob/master/src/model/validations/validators.coffee) for the structure of these messages.

## @.defaultLocale[= "en"] : String

The default locale for `Batman.I18N`. Override this to make your app start in a locale other than `"en"`.

## @%locale : String

The current locale for `Batman.I18N`, used by `Batman.translate`. Set this value to change your locale:

```coffeescript
Batman.I18N.set('locale', 'fr') # Bienvenue!
```

## @%locales : I18N.LocalesStorage

Returns the current `Batman.I18N.LocalesStorage`, which stores locales and there translations. You can set keys on `locales` to define translations:

```coffeescript
Batman.I18N.set("locales.uz", { ... })
```

See ["Getting locales from the server"](/docs/api/batman.i18n.html#getting_locales_from_the_server) for information about how `Batman.I18N.LocalesStorage` handles missing keys.

## @%translations : Object

Returns the defined translations for the current locale.

## @enable()

Turns on `Batman.I18N` by:

- Storing batman.js's default error messages to the `"en"` locale
- Overriding `Batman.translate` to use `Batman.I18N.translate`

## @disable()

Turns off `Batman.I18N` by restoring previous functionality to `Batman.translate`.


Animated the head.

Animated the head.

# game

This module contains your game logic.  Its methods are called by the "index"
module to update the game state.

    gamepad = require "./input/gamepad.litcoffee"
    keyboard = require "./input/keyboard.litcoffee"

    context = require "./rendering/context.litcoffee"
    cloud = require "./rendering/cloud.litcoffee"
    matrix = require "./matrix.litcoffee"
    plane = require "./plane.litcoffee"
    misc = require "./misc.litcoffee"
    navmesh = require "./navmesh.litcoffee"
    vector = require "./vector.litcoffee"
    pose = require "./pose.litcoffee"
    ik = require "./ik.litcoffee"

    triangle = undefined

    character = testMap = testNavmesh = undefined
    characterData = require "./characters/test/cloud.coffee"

# load
Called before the first "tick"/"draw" to load any resources needed by the game.
Execute the argument when you have finished successfully.

    load = (callback) ->
        keyboard()
        context.load()
        cloud.load characterData, (character_) ->
            character = character_
            cloud.load (require "./tracks/test/geometry.msc"), (testMap_) ->
                testMap = testMap_
                navmesh.load (require "./tracks/test/navmesh.msn"), (testNavmesh_) ->
                    testNavmesh = testNavmesh_
                    triangle = testNavmesh[0]
                    callback()
                    return
                return
            return
        return

# tick
Called at 20Hz to update game state.  Guaranteed to be called once between
"load" and "draw".  May be called more or than once per call to "draw" or not at
all.

    firstTick = true
    firstDraw = true

    entityTransform = []
    entityTransformToDraw = []
    entityTransformPreviousTick = []
    entityTransformPreviousDraw = []
    matrix.identity entityTransform

    playerPoseOld = pose.create characterData
    playerPoseNew = pose.create characterData

    torsoTransform = []
    torsoTransformToDraw = []
    torsoTransformPreviousTick = []
    torsoTransformPreviousDraw = []

    surfaceCorrectedEntityTransform = []
    matrix.identity surfaceCorrectedEntityTransform

    cameraTransform = []
    cameraTransformPreviousDraw = []
    matrix.identity cameraTransform

    torsoAltitude = 0

    velocity = [0, 0, 0]
    xAxis = []
    yAxis = []
    zAxis = []
    torsoXAxis = []
    torsoZAxis = []
    translation = []
    targetForwardVelocity = []
    targetSidewardVelocity = []
    targetVelocity = []
    torsoTranslation = []

    start = []
    leftFoot = []
    rightFoot = []
    walked = 0
    shuffleTicks = 0
    foot = "left"
    offset = []

    yaw = 0
    pitch = 0
    headYawPreviousTick = headYaw = 0

    stick = [0, 0, 0]
    stickAngle = 0

    edgeCallback = (edge) -> vector.flatten velocity, edge.plane.normal, velocity

    tick = ->
        if not firstTick
            matrix.copy entityTransform, entityTransformPreviousTick
            matrix.copy torsoTransform, torsoTransformPreviousTick

        matrix.translate velocity, entityTransform
        speed = vector.magnitude velocity
        walked += speed
        moving = speed > 2

        stick[0] = gamepad.right - gamepad.left
        stick[1] = gamepad.forward - gamepad.backward
        stickLength = vector.magnitude stick

        inDeadzone = stickLength < 0.1

        if inDeadzone
            stick[0] = stick[1] = 0
        else
            stickAngle = Math.atan2 -stick[0], stick[1]
            if stickLength > 1
                vector.divide.byScalar stick, stickLength, stick
                stickLength = 1

        # Left/right rotation.
        matrix.rotateY (stick[0] * -0.1), entityTransform, true

        # Apply navmesh triangle collision.
        matrix.getTranslation entityTransform, translation
        triangle = navmesh.constrain translation, triangle, edgeCallback

        # Gravity.
        velocity[1] -= 0.2

        # Forwards/backwards movement.
        matrix.getZ entityTransform, zAxis

        # Determine if we're underground.
        altitude = plane.distance triangle.plane, translation

        if altitude < 0
            velocity = velocity
            plane.project triangle.plane, translation, translation

            # A "bump" when you land.
            matrix.getY entityTransform, yAxis
            impact = Math.abs (vector.dot triangle.plane.normal, velocity) * (vector.dot triangle.plane.normal, yAxis)
            impact -= 1
            if impact < 0 then impact = 0
            impact *= 2
            torsoAltitude -= impact

            # Reflecting the velocity against the hit surface but then
            # flattening it has the effect that hitting a ramp bounces you up it
            # but you don't bounce when falling onto the floor.
            vector.reflect velocity, triangle.plane.normal, velocity
            vector.flatten velocity, triangle.plane.normal, velocity

            if not inDeadzone
                matrix.getX entityTransform, xAxis
                vector.multiply.byScalar zAxis, (stick[1] * 10), targetForwardVelocity
                vector.multiply.byScalar xAxis, (stick[0] * 10), targetSidewardVelocity
                vector.add.vector targetForwardVelocity, targetSidewardVelocity, targetVelocity
            else
                targetVelocity[0] = targetVelocity[1] = targetVelocity[2] = 0
            vector.interpolate velocity, targetVelocity, 0.05, velocity

        # Apply air resistance.
        vector.multiply.byScalar velocity, 0.98, velocity

        # This section attempts to align the entity with the triangle surface.

        # We take the triangle's normal as the Y/up axis.

        # First, flatten the Z/forward axis onto the surface.
        vector.flatten zAxis, triangle.plane.normal, zAxis
        vector.normalize zAxis, zAxis

        # Then, find the X/right axis which is perpendicular to the Z/forward
        # and Y/up axes.
        vector.cross triangle.plane.normal, zAxis, xAxis
        matrix.setX xAxis, surfaceCorrectedEntityTransform
        matrix.setY triangle.plane.normal, surfaceCorrectedEntityTransform
        matrix.setZ zAxis, surfaceCorrectedEntityTransform

        # Blend between our current transform and the aligned one.
        matrix.interpolate entityTransform, surfaceCorrectedEntityTransform, 0.1, entityTransform

        matrix.setTranslation translation, entityTransform

        matrix.copy entityTransform, torsoTransform

        torsoAltitude = misc.interpolate torsoAltitude, (12 - Math.min 2, (speed + if inDeadzone then 0 else 1.5)), 0.4

        matrix.getY entityTransform, yAxis
        vector.multiply.byScalar yAxis, torsoAltitude, torsoTranslation
        vector.add.vector torsoTranslation, translation, torsoTranslation

        yaw = misc.interpolateAngle yaw, stickAngle, 0.1, yaw
        matrix.rotateY yaw, torsoTransform, true

        # Calculating the head angle relative to the torso is complicated by the
        # stick angle being wrapped to 0...2PI, so we have to shift it out of
        # that range to clamp it.
        headYawPreviousTick = headYaw
        headYaw = Math.max Math.PI - 1, Math.min Math.PI + 1, misc.wrapAngle Math.PI + stickAngle - yaw
        headYaw -= Math.PI
        headYaw = misc.interpolateAngle headYawPreviousTick, headYaw, 0.4, headYaw

        matrix.getZ torsoTransform, torsoZAxis # TODO: If the entity transform is ever non-identity at startup we'll need to initialize torsoTransform for this to be right in the first ticks.
        matrix.getX torsoTransform, torsoXAxis # TODO: If the entity transform is ever non-identity at startup we'll need to initialize torsoTransform for this to be right in the first ticks.
        pitch = misc.interpolate pitch, (0.5 * stickLength - 0.1 * vector.dot torsoZAxis, velocity), 0.2
        matrix.rotateX pitch, torsoTransform, true
        matrix.rotateZ (0.1 * vector.dot torsoXAxis, velocity), torsoTransform, true

        matrix.setTranslation torsoTranslation, torsoTransform

        if walked < 5
            shuffleTicks--
        while walked > 5 or shuffleTicks < 0
            shuffleTicks = 5
            walked -= 5
            if (not inDeadzone) or moving
                vector.normalize velocity, offset
                vector.multiply.byScalar offset, 5, offset
            else
                offset[0] = offset[1] = offset[2] = 0
            foot = switch foot
                when "left"
                    matrix.applyToVector playerPoseNew.torso, [-2, -10, 0], leftFoot
                    vector.add.vector offset, leftFoot, leftFoot
                    plane.project triangle.plane, leftFoot, leftFoot
                    "right"
                when "right"
                    matrix.applyToVector playerPoseNew.torso, [2, -10, 0], rightFoot
                    vector.add.vector offset, rightFoot, rightFoot
                    plane.project triangle.plane, rightFoot, rightFoot
                    "left"

        if firstTick
            matrix.copy entityTransform, entityTransformPreviousTick
            matrix.copy torsoTransform, torsoTransformPreviousTick
            firstTick = false
        return

# draw
Called with the progress through the current frame (a number between 0 and 1) to
redraw the scene.

    draw = (progress) ->

        if not firstDraw
            matrix.copy entityTransformToDraw, entityTransformPreviousDraw
            matrix.copy cameraTransform, cameraTransformPreviousDraw
            matrix.copy torsoTransformToDraw, torsoTransformPreviousDraw
            pose.copy playerPoseNew, playerPoseOld

        matrix.interpolate entityTransformPreviousTick, entityTransform, progress, entityTransformToDraw
        matrix.interpolate torsoTransformPreviousTick, torsoTransform, progress, playerPoseNew.torso

        matrix.invert entityTransformToDraw, cameraTransform
        matrix.translate [0,-10, 10], cameraTransform

        matrix.applyToVector playerPoseNew.torso, [-1.5, -2.5, 0], start
        matrix.getX playerPoseNew.torso, xAxis
        ik.computeLimb start, leftFoot, 10, xAxis, playerPoseNew.legLeftUpper, playerPoseNew.legLeftLower

        matrix.applyToVector playerPoseNew.torso, [1.5, -2.5, 0], start
        ik.computeLimb start, rightFoot, 10, xAxis, playerPoseNew.legRightUpper, playerPoseNew.legRightLower

        matrix.copy playerPoseNew.torso, playerPoseNew.head
        matrix.translate [0, 6, 0], playerPoseNew.head, true
        matrix.rotateY (misc.interpolate headYawPreviousTick, headYaw, progress), playerPoseNew.head, true

        if firstDraw
            matrix.copy entityTransformToDraw, entityTransformPreviousDraw
            matrix.copy cameraTransform, cameraTransformPreviousDraw
            pose.copy playerPoseNew, playerPoseOld
            firstDraw = false

        context.begin 0, 0, context.width, context.height, 1, cameraTransformPreviousDraw, cameraTransform, 0.1, 0.5, 0.9
        cloud.draw testMap
        cloud.draw character, playerPoseOld, playerPoseNew

        return

    module.exports = { load, tick, draw }

# game

This module contains your game logic.  Its methods are called by the "index"
module to update the game state.

    gamepad = require "./input/gamepad.litcoffee"
    keyboard = require "./input/keyboard.litcoffee"

    context = require "./rendering/context.litcoffee"
    cloud = require "./rendering/cloud.litcoffee"
    matrix = require "./matrix.litcoffee"
    plane = require "./plane.litcoffee"
    misc = require "./misc.litcoffee"
    navmesh = require "./navmesh.litcoffee"
    vector = require "./vector.litcoffee"
    pose = require "./pose.litcoffee"
    ik = require "./ik.litcoffee"

    triangle = undefined

    character = testMap = testNavmesh = undefined
    characterData = require "./characters/test/cloud.coffee"

# load
Called before the first "tick"/"draw" to load any resources needed by the game.
Execute the argument when you have finished successfully.

    load = (callback) ->
        keyboard()
        context.load()
        cloud.load characterData, (character_) ->
            character = character_
            cloud.load (require "./tracks/test/geometry.msc"), (testMap_) ->
                testMap = testMap_
                navmesh.load (require "./tracks/test/navmesh.msn"), (testNavmesh_) ->
                    testNavmesh = testNavmesh_
                    triangle = testNavmesh[0]
                    callback()
                    return
                return
            return
        return

# tick
Called at 20Hz to update game state.  Guaranteed to be called once between
"load" and "draw".  May be called more or than once per call to "draw" or not at
all.

    firstTick = true
    firstDraw = true

    entityTransform = []
    entityTransformToDraw = []
    entityTransformPreviousTick = []
    entityTransformPreviousDraw = []
    matrix.identity entityTransform

    playerPoseOld = pose.create characterData
    playerPoseNew = pose.create characterData

    torsoTransform = []
    torsoTransformToDraw = []
    torsoTransformPreviousTick = []
    torsoTransformPreviousDraw = []

    surfaceCorrectedEntityTransform = []
    matrix.identity surfaceCorrectedEntityTransform

    cameraTransform = []
    cameraTransformPreviousDraw = []
    matrix.identity cameraTransform

    torsoAltitude = 0

    velocity = [0, 0, 0]
    xAxis = []
    yAxis = []
    zAxis = []
    torsoXAxis = []
    torsoZAxis = []
    translation = []
    targetForwardVelocity = []
    targetSidewardVelocity = []
    targetVelocity = []
    torsoTranslation = []

    start = []
    leftFoot = []
    rightFoot = []
    walked = 0
    shuffleTicks = 0
    foot = "left"
    offset = []

    yaw = 0
    pitch = 0

    stick = [0, 0, 0]
    stickAngle = 0

    edgeCallback = (edge) -> vector.flatten velocity, edge.plane.normal, velocity

    tick = ->
        if not firstTick
            matrix.copy entityTransform, entityTransformPreviousTick
            matrix.copy torsoTransform, torsoTransformPreviousTick

        matrix.translate velocity, entityTransform
        speed = vector.magnitude velocity
        walked += speed
        moving = speed > 2

        stick[0] = gamepad.right - gamepad.left
        stick[1] = gamepad.forward - gamepad.backward
        stickLength = vector.magnitude stick

        inDeadzone = stickLength < 0.1

        if inDeadzone
            stick[0] = stick[1] = 0
        else
            stickAngle = Math.atan2 -stick[0], stick[1]
            if stickLength > 1
                vector.divide.byScalar stick, stickLength, stick
                stickLength = 1

        # Left/right rotation.
        matrix.rotateY (stick[0] * -0.1), entityTransform, true

        # Apply navmesh triangle collision.
        matrix.getTranslation entityTransform, translation
        triangle = navmesh.constrain translation, triangle, edgeCallback

        # Gravity.
        velocity[1] -= 0.2

        # Forwards/backwards movement.
        matrix.getZ entityTransform, zAxis

        # Determine if we're underground.
        altitude = plane.distance triangle.plane, translation

        if altitude < 0
            velocity = velocity
            plane.project triangle.plane, translation, translation

            # A "bump" when you land.
            matrix.getY entityTransform, yAxis
            impact = Math.abs (vector.dot triangle.plane.normal, velocity) * (vector.dot triangle.plane.normal, yAxis)
            impact -= 1
            if impact < 0 then impact = 0
            impact *= 2
            torsoAltitude -= impact

            # Reflecting the velocity against the hit surface but then
            # flattening it has the effect that hitting a ramp bounces you up it
            # but you don't bounce when falling onto the floor.
            vector.reflect velocity, triangle.plane.normal, velocity
            vector.flatten velocity, triangle.plane.normal, velocity

            if not inDeadzone
                matrix.getX entityTransform, xAxis
                vector.multiply.byScalar zAxis, (stick[1] * 10), targetForwardVelocity
                vector.multiply.byScalar xAxis, (stick[0] * 10), targetSidewardVelocity
                vector.add.vector targetForwardVelocity, targetSidewardVelocity, targetVelocity
            else
                targetVelocity[0] = targetVelocity[1] = targetVelocity[2] = 0
            vector.interpolate velocity, targetVelocity, 0.05, velocity

        # Apply air resistance.
        vector.multiply.byScalar velocity, 0.98, velocity

        # This section attempts to align the entity with the triangle surface.

        # We take the triangle's normal as the Y/up axis.

        # First, flatten the Z/forward axis onto the surface.
        vector.flatten zAxis, triangle.plane.normal, zAxis
        vector.normalize zAxis, zAxis

        # Then, find the X/right axis which is perpendicular to the Z/forward
        # and Y/up axes.
        vector.cross triangle.plane.normal, zAxis, xAxis
        matrix.setX xAxis, surfaceCorrectedEntityTransform
        matrix.setY triangle.plane.normal, surfaceCorrectedEntityTransform
        matrix.setZ zAxis, surfaceCorrectedEntityTransform

        # Blend between our current transform and the aligned one.
        matrix.interpolate entityTransform, surfaceCorrectedEntityTransform, 0.1, entityTransform

        matrix.setTranslation translation, entityTransform

        matrix.copy entityTransform, torsoTransform

        torsoAltitude = misc.interpolate torsoAltitude, (12 - Math.min 2, (speed + if inDeadzone then 0 else 1.5)), 0.4

        matrix.getY entityTransform, yAxis
        vector.multiply.byScalar yAxis, torsoAltitude, torsoTranslation
        vector.add.vector torsoTranslation, translation, torsoTranslation

        yaw = misc.interpolateAngle yaw, stickAngle, 0.2, yaw
        matrix.rotateY yaw, torsoTransform, true

        matrix.getZ torsoTransform, torsoZAxis # TODO: If the entity transform is ever non-identity at startup we'll need to initialize torsoTransform for this to be right in the first ticks.
        matrix.getX torsoTransform, torsoXAxis # TODO: If the entity transform is ever non-identity at startup we'll need to initialize torsoTransform for this to be right in the first ticks.
        pitch = misc.interpolate pitch, (0.5 * stickLength - 0.1 * vector.dot torsoZAxis, velocity), 0.2
        matrix.rotateX pitch, torsoTransform, true
        matrix.rotateZ (0.1 * vector.dot torsoXAxis, velocity), torsoTransform, true

        matrix.setTranslation torsoTranslation, torsoTransform

        if walked < 5
            shuffleTicks--
        while walked > 5 or shuffleTicks < 0
            shuffleTicks = 5
            walked -= 5
            if (not inDeadzone) or moving
                vector.normalize velocity, offset
                vector.multiply.byScalar offset, 5, offset
            else
                offset[0] = offset[1] = offset[2] = 0
            foot = switch foot
                when "left"
                    matrix.applyToVector playerPoseNew.torso, [-2, -10, 0], leftFoot
                    vector.add.vector offset, leftFoot, leftFoot
                    plane.project triangle.plane, leftFoot, leftFoot
                    "right"
                when "right"
                    matrix.applyToVector playerPoseNew.torso, [2, -10, 0], rightFoot
                    vector.add.vector offset, rightFoot, rightFoot
                    plane.project triangle.plane, rightFoot, rightFoot
                    "left"

        if firstTick
            matrix.copy entityTransform, entityTransformPreviousTick
            matrix.copy torsoTransform, torsoTransformPreviousTick
            firstTick = false
        return

# draw
Called with the progress through the current frame (a number between 0 and 1) to
redraw the scene.

    draw = (progress) ->

        if not firstDraw
            matrix.copy entityTransformToDraw, entityTransformPreviousDraw
            matrix.copy cameraTransform, cameraTransformPreviousDraw
            matrix.copy torsoTransformToDraw, torsoTransformPreviousDraw
            pose.copy playerPoseNew, playerPoseOld

        matrix.interpolate entityTransformPreviousTick, entityTransform, progress, entityTransformToDraw
        matrix.interpolate torsoTransformPreviousTick, torsoTransform, progress, playerPoseNew.torso

        matrix.invert entityTransformToDraw, cameraTransform
        matrix.translate [0,-10, 10], cameraTransform

        matrix.applyToVector playerPoseNew.torso, [-1.5, -2.5, 0], start
        matrix.getX playerPoseNew.torso, xAxis
        ik.computeLimb start, leftFoot, 10, xAxis, playerPoseNew.legLeftUpper, playerPoseNew.legLeftLower

        matrix.applyToVector playerPoseNew.torso, [1.5, -2.5, 0], start
        ik.computeLimb start, rightFoot, 10, xAxis, playerPoseNew.legRightUpper, playerPoseNew.legRightLower

        if firstDraw
            matrix.copy entityTransformToDraw, entityTransformPreviousDraw
            matrix.copy cameraTransform, cameraTransformPreviousDraw
            pose.copy playerPoseNew, playerPoseOld
            firstDraw = false

        context.begin 0, 0, context.width, context.height, 1, cameraTransformPreviousDraw, cameraTransform, 0.1, 0.5, 0.9
        cloud.draw testMap
        cloud.draw character, playerPoseOld, playerPoseNew

        return

    module.exports = { load, tick, draw }


router errors

router errors

#Weya.Router.litcoffee

    @Weya = {} unless @Weya?
    Weya = @Weya

    if exports?
     Weya = require './weya'
     Weya.Base = require './weya_base'

    if not Weya.Base?
     throw new Error 'Weya.Base not found'

##Weya.Router

Routing with hash tags or pushState

    class Router extends Weya.Base
     optionalParam: /\((.*?)\)/g
     namedParam: /(\(\?)?:\w+/g
     splatParam: /\*\w+/g
     escapeRegExp: /[\-{}\[\]+?.,\\\^$|#\s]/g

     @extend()

     @initialize ->
      @_bindRoutes()
      @_event = null
      @_history = []

     _routes: {}

####Register router

`'analyse/:analysis': 'onAnalyse'`

`dashboard': ['auth', 'dashboard']`

This will match urls of the form `analyse/\*` and call method @onAnalyse
with the parameter. Second route will first call @auth if it returns
`true` @dashboard will be called.

The most specific route should be at bottom. Routers can added or overridden
in subclasses

     @routes: (routes) ->
      temp = @::_routes
      @::_routes = {}
      @::_routes[k] = v for k, v of temp
      @::_routes[k] = v for k, v of routes

####Starts routing
Option silent will not trigger an event for the current url

     start: (options) ->
      Weya.history.start options
      fragment = Weya.history.getFragment()

      if options?.silent is on
       @_history.push fragment: fragment, title: document.title

####Goes to previous page if exists

     back: ->
      if @_history.length > 1
       Weya.history.back()

####Whether it is possible to go to previous page

     canBack: ->
      if @_history.length > 1 and Weya.history.canBack()
       return true
      else
       return false

####Registers a route

     route: (route, name) ->
      if (Object.prototype.toString.call route) isnt '[object RegExp]'
       route = @_routeToRegExp route

Registers the route with Weya.history

      Weya.history.route route, (fragment, event) =>
       args = @_extractParameters route, fragment
       @_event = event

Handle state

       if @_event?.type is "popstate"
        @_history.pop()
        if @_history.length is 0
         @_history.push
          fragment: fragment
          title: document.title
          state: @getState()
       else
        @_history.push
         fragment: fragment
         title: document.title
         state: @getState()

Calls callbacks in order

       callbacks = name
       callbacks = [callbacks] if not Array.isArray callbacks

       for callback in callbacks
        if (typeof callback) is 'string'
         callback = @[callback]
        break unless callback.apply this, args

####Gets the current HTML5 History state

     getState: ->
      if @_event?.originalEvent?.state?
       return @_event.originalEvent.state
      else
       return null

####Navigate to a new URL, while setting HTML5 History state

     navigate: (fragment, options) ->
      options = {} unless options
      if options.replace
       @_history.pop()
      if not options.trigger
       @_history.push
        fragment: fragment
        title: options.title
        state: options.state

      Weya.history.navigate fragment, options

Bind routes

     _bindRoutes: ->
      for route, name of @_routes
       @route route, name

Convert a route string into a regular expression, suitable for matching

     _routeToRegExp: (route) ->
      route = route.replace @escapeRegExp, '\\$&'
                   .replace @noptionalParam, '(?:$1)?'
                   .replace @namedParam, (match, optional) ->
                    if optional then match else '([^\/]+)'
                   .replace @splatParam, '(.*?)'

       return new RegExp "^#{route}$"

Extract parameters from a route regex and URL

     _extractParameters: (route, fragment) ->
      params = route.exec(fragment).slice(1)
      for p, i in params when p?
       params[i] = decodeURIComponent p

## History class

    class History extends Weya.Base
     @extend()

Strip leading hash/slash and trailing space

     routeStripper: /^[#\/]|\s+$/g

Strip leading and trailing slashes

     rootStripper: /^\/+|\/+$/g

Remove trailing slash

     trailingSlash: /\/$/

Strip urls of hash and query

     pathStripper: /[?#].*$/

     @initialize ->
      @handlers = []
      @checkUrl = @checkUrl.bind this
      @history = window.history
      @location = window.location
      @stateList = []

Hash check interval

     interval: 50

Get hash value

     getHash: ->
      match = @location.href.match /#(.*)$/
      return (if match? then match[1] else '')

Get emulated state

     getEmulateState: ->
      if @stateList.length > 0
       return @stateList[@stateList.length - 1]
      else
       return {fragment: ""}

     popEmulateState: -> @stateList.pop()

     pushEmulateState: (state, title, fragment) ->
      @stateList.push
       state: state
       title: title
       fragment: fragment

Get the URL fragment

     getFragment: (fragment, forcePushState) ->
      if not fragment?
       if @_emulateState
        fragment = @getEmulateState().fragment
       else if @_hasPushState or not @_wantsHashChange or forcePushState
        fragment = @location.pathname
        root = @root.replace @trailingSlash, ''
        if (fragment.indexOf root) is 0
         fragment = fragment.slice root.length
       else
        fragment = @getHash()

      return fragment.replace @routeStripper, ''

####Goto the previous page

     back: ->
      if @_emulateState is on
       @popEmulateState()
       @loadUrl null, null
      else
       @history?.back?()

####Can back?

     canBack: ->
      if @_emulateState is on
       return @stateList.length > 1
      else
       return @history?.back?

####Start listening to events

     start: (options) ->
      History.started = true

      opt = {root: '/'}
      opt[k] = v for k, v of @options
      opt[k] = v for k, v of options
      @options = opt
      @root = @options.root
      @_emulateState = @options.emulateState is on
      @_wantsHashChange = @_emulateState is off and @options.hashChange isnt off
      @_wantsPushState = @_emulateState is off and @options.pushState is on
      @_hasPushState = @_wantsPushState is on and @history?.pushState?
      if @_emulateState and @options.start?
       @pushEmulateState @options.start.state,
        @options.start.title
        @options.start.fragment

      @fragment = @getFragment()

Normalize root to always include a leading and trailing slash.

      @root = "/#{@root}/".replace @rootStripper, '/'

Depending on whether we're using pushState or hashes, and whether
'onhashchange' is supported, determine how we check the URL state.

      if @_hasPushState
       window.onpopstate = @checkUrl
      else if @_wantsHashChange and window.onhashchange?
       window.onhashchange = @checkUrl
      else if @_wantsHashChange
       @_checkUrlInterval = setInterval @checkUrl, @interval

      if not @options.silent
       @loadUrl null, null

####Add a listener to a router

     route: (route, callback) ->
      @handlers.unshift route: route, callback: callback

Check current url for changes

     checkUrl: (e) ->
      fragment = @getFragment()
      return if fragment is @fragment
      @loadUrl fragment, e

Call callbacks of matching route

     loadUrl: (fragment, e) ->
      try
       fragment = @fragment = @getFragment fragment
       for handler in @handlers
        if handler.route.test fragment
         return handler.callback fragment, e
      catch e
       @onerror? e

      return

####Navigate to a URL
Triggers a route is option trigger is on
Replaces the url is option replace is on
Sets state and title

     navigate: (fragment, options) ->
      return false if not History.started

      fragment = @getFragment(fragment or '')
      url = @root + fragment

Strip the fragment of the query and hash for matching.

      fragment = fragment.replace @pathStripper, ''

      return if @fragment is fragment
      @fragment = fragment

Don't include a trailing slash on the root.

      if fragment is '' and url isnt '/'
       url = url.slice 0, -1

If pushState is available, we use it to set the fragment as a real URL.

      if @_emulateState
       if options.replace is on
        @popEmulateState()
       state = {}
       state = options.state if options.state?
       title = ''
       title = options.title if options.title?
       @pushEmulateState state, title, fragment
      else if @_hasPushState
       method = if options.replace then 'replaceState' else 'pushState'
       state = {}
       state = options.state if options.state?
       title = ''
       title = options.title if options.title?
       @history[method] state, title, url

If hash changes haven't been explicitly disabled, update the hash
fragment to store history.

      else if @_wantsHashChange
       @_updateHash @location, fragment, options.replace

If you've told us that you explicitly don't want fallback hashchange-
based history, then `navigate` becomes a page refresh.

      else
       return @location.assign url
      if options.trigger
       return @loadUrl fragment, null

Update the hash

     _updateHash: (location, fragment, replace) ->
      if replace
       href = location.href.replace /(javascript:|#).*$/, ''
       location.replace "#{href}##{fragment}"
      else
       location.hash = "##{fragment}"

    Weya.Router = Router
    Weya.history = new History

    if module?
     module.exports = Weya.Router

#Weya.Router.litcoffee

    @Weya = {} unless @Weya?
    Weya = @Weya

    if exports?
     Weya = require './weya'
     Weya.Base = require './weya_base'

    if not Weya.Base?
     throw new Error 'Weya.Base not found'

##Weya.Router

Routing with hash tags or pushState

    class Router extends Weya.Base
     optionalParam: /\((.*?)\)/g
     namedParam: /(\(\?)?:\w+/g
     splatParam: /\*\w+/g
     escapeRegExp: /[\-{}\[\]+?.,\\\^$|#\s]/g

     @extend()

     @initialize ->
      @_bindRoutes()
      @_event = null
      @_history = []

     _routes: {}

####Register router

`'analyse/:analysis': 'onAnalyse'`

`dashboard': ['auth', 'dashboard']`

This will match urls of the form `analyse/\*` and call method @onAnalyse
with the parameter. Second route will first call @auth if it returns
`true` @dashboard will be called.

The most specific route should be at bottom. Routers can added or overridden
in subclasses

     @routes: (routes) ->
      temp = @::_routes
      @::_routes = {}
      @::_routes[k] = v for k, v of temp
      @::_routes[k] = v for k, v of routes

####Starts routing
Option silent will not trigger an event for the current url

     start: (options) ->
      Weya.history.start options
      fragment = Weya.history.getFragment()

      if options?.silent is on
       @_history.push fragment: fragment, title: document.title

####Goes to previous page if exists

     back: ->
      if @_history.length > 1
       Weya.history.back()

####Whether it is possible to go to previous page

     canBack: ->
      if @_history.length > 1 and Weya.history.canBack()
       return true
      else
       return false

####Registers a route

     route: (route, name) ->
      if (Object.prototype.toString.call route) isnt '[object RegExp]'
       route = @_routeToRegExp route

Registers the route with Weya.history

      Weya.history.route route, (fragment, event) =>
       args = @_extractParameters route, fragment
       @_event = event

Handle state

       if @_event?.type is "popstate"
        @_history.pop()
        if @_history.length is 0
         @_history.push
          fragment: fragment
          title: document.title
          state: @getState()
       else
        @_history.push
         fragment: fragment
         title: document.title
         state: @getState()

Calls callbacks in order

       callbacks = name
       callbacks = [callbacks] if not Array.isArray callbacks

       for callback in callbacks
        if (typeof callback) is 'string'
         callback = @[callback]
        break unless callback.apply this, args

####Gets the current HTML5 History state

     getState: ->
      if @_event?.originalEvent?.state?
       return @_event.originalEvent.state
      else
       return null

####Navigate to a new URL, while setting HTML5 History state

     navigate: (fragment, options) ->
      options = {} unless options
      if options.replace
       @_history.pop()
      if not options.trigger
       @_history.push
        fragment: fragment
        title: options.title
        state: options.state

      Weya.history.navigate fragment, options

Bind routes

     _bindRoutes: ->
      for route, name of @_routes
       @route route, name

Convert a route string into a regular expression, suitable for matching

     _routeToRegExp: (route) ->
      route = route.replace @escapeRegExp, '\\$&'
                   .replace @noptionalParam, '(?:$1)?'
                   .replace @namedParam, (match, optional) ->
                    if optional then match else '([^\/]+)'
                   .replace @splatParam, '(.*?)'

       return new RegExp "^#{route}$"

Extract parameters from a route regex and URL

     _extractParameters: (route, fragment) ->
      params = route.exec(fragment).slice(1)
      for p, i in params when p?
       params[i] = decodeURIComponent p

## History class

    class History extends Weya.Base
     @extend()

Strip leading hash/slash and trailing space

     routeStripper: /^[#\/]|\s+$/g

Strip leading and trailing slashes

     rootStripper: /^\/+|\/+$/g

Remove trailing slash

     trailingSlash: /\/$/

Strip urls of hash and query

     pathStripper: /[?#].*$/

     @initialize ->
      @handlers = []
      @checkUrl = @checkUrl.bind this
      @history = window.history
      @location = window.location
      @stateList = []

Hash check interval

     interval: 50

Get hash value

     getHash: ->
      match = @location.href.match /#(.*)$/
      return (if match? then match[1] else '')

Get emulated state

     getEmulateState: ->
      if @stateList.length > 0
       return @stateList[@stateList.length - 1]
      else
       return {fragment: ""}

     popEmulateState: -> @stateList.pop()

     pushEmulateState: (state, title, fragment) ->
      @stateList.push
       state: state
       title: title
       fragment: fragment

Get the URL fragment

     getFragment: (fragment, forcePushState) ->
      if not fragment?
       if @_emulateState
        fragment = @getEmulateState().fragment
       else if @_hasPushState or not @_wantsHashChange or forcePushState
        fragment = @location.pathname
        root = @root.replace @trailingSlash, ''
        if (fragment.indexOf root) is 0
         fragment = fragment.slice root.length
       else
        fragment = @getHash()

      return fragment.replace @routeStripper, ''

####Goto the previous page

     back: ->
      if @_emulateState is on
       @popEmulateState()
       @loadUrl null, null
      else
       @history?.back?()

####Can back?

     canBack: ->
      if @_emulateState is on
       return @stateList.length > 1
      else
       return @history?.back?

####Start listening to events

     start: (options) ->
      History.started = true

      opt = {root: '/'}
      opt[k] = v for k, v of @options
      opt[k] = v for k, v of options
      @options = opt
      @root = @options.root
      @_emulateState = @options.emulateState is on
      @_wantsHashChange = @_emulateState is off and @options.hashChange isnt off
      @_wantsPushState = @_emulateState is off and @options.pushState is on
      @_hasPushState = @_wantsPushState is on and @history?.pushState?
      if @_emulateState and @options.start?
       @pushEmulateState @options.start.state,
        @options.start.title
        @options.start.fragment

      @fragment = @getFragment()

Normalize root to always include a leading and trailing slash.

      @root = "/#{@root}/".replace @rootStripper, '/'

Depending on whether we're using pushState or hashes, and whether
'onhashchange' is supported, determine how we check the URL state.

      if @_hasPushState
       window.onpopstate = @checkUrl
      else if @_wantsHashChange and window.onhashchange?
       window.onhashchange = @checkUrl
      else if @_wantsHashChange
       @_checkUrlInterval = setInterval @checkUrl, @interval

      if not @options.silent
       @loadUrl null, null

####Add a listener to a router

     route: (route, callback) ->
      @handlers.unshift route: route, callback: callback

Check current url for changes

     checkUrl: (e) ->
      fragment = @getFragment()
      return if fragment is @fragment
      @loadUrl fragment, e

Call callbacks of matching route

     loadUrl: (fragment, e) ->
      fragment = @fragment = @getFragment fragment
      for handler in @handlers
       if handler.route.test fragment
        return handler.callback fragment, e

      return

####Navigate to a URL
Triggers a route is option trigger is on
Replaces the url is option replace is on
Sets state and title

     navigate: (fragment, options) ->
      return false if not History.started

      fragment = @getFragment(fragment or '')
      url = @root + fragment

Strip the fragment of the query and hash for matching.

      fragment = fragment.replace @pathStripper, ''

      return if @fragment is fragment
      @fragment = fragment

Don't include a trailing slash on the root.

      if fragment is '' and url isnt '/'
       url = url.slice 0, -1

If pushState is available, we use it to set the fragment as a real URL.

      if @_emulateState
       if options.replace is on
        @popEmulateState()
       state = {}
       state = options.state if options.state?
       title = ''
       title = options.title if options.title?
       @pushEmulateState state, title, fragment
      else if @_hasPushState
       method = if options.replace then 'replaceState' else 'pushState'
       state = {}
       state = options.state if options.state?
       title = ''
       title = options.title if options.title?
       @history[method] state, title, url

If hash changes haven't been explicitly disabled, update the hash
fragment to store history.

      else if @_wantsHashChange
       @_updateHash @location, fragment, options.replace

If you've told us that you explicitly don't want fallback hashchange-
based history, then `navigate` becomes a page refresh.

      else
       return @location.assign url
      if options.trigger
       return @loadUrl fragment, null

Update the hash

     _updateHash: (location, fragment, replace) ->
      if replace
       href = location.href.replace /(javascript:|#).*$/, ''
       location.replace "#{href}##{fragment}"
      else
       location.hash = "##{fragment}"

    Weya.Router = Router
    Weya.history = new History

    if module?
     module.exports = Weya.Router


Add create/select/delete branch

Add create/select/delete branch

Dependencies
============

    _      = require "underscore"
    fs     = require "fs"
    path   = require "path"
    colors = require "cli-color"

VOSCO
=====

    script =

Setup
-----

      'install': () ->
        await @vosco.isInstalled defer(error, isInstalled)
        if isInstalled then process.exit 1
        await @vosco.install defer(error)

      'uninstall': () ->
        await @vosco.isInstalled defer(error, isInstalled)
        unless isInstalled then process.exit 1
        await @vosco.uninstall defer(error)

Repository
----------

      'get-status': () ->
        await @vosco.isInstalled defer(error, isInstalled)
        unless isInstalled then process.exit 1
        await @vosco.getStatus defer(error, status)
        getType = (file) -> file.type
        getPath = (file) -> colors.xterm(240)("  #{file.path}")
        groups  = _.groupBy status, getType
        for group, paths of groups
          console.log """
          #{group}
          #{paths.map(getPath).join("\n")}
          """

      'get-history': () ->
        await @vosco.isInstalled defer(error, isInstalled)
        unless isInstalled then process.exit 1
        await @vosco.getHistory defer(error, history)
        for commit in history
          hash = commit.hash.substr(0, 7)
          date = new Date(commit.date).toDateString()
          msg  = commit.message
          console.log colors.xterm(240)("  #{hash}  #{date}  #{msg}")

      'get-content-history': (path) ->
        await @vosco.isInstalled defer(error, isInstalled)
        unless isInstalled then process.exit 1
        # show warning if file doesn"t exist
        console.log "content history"

Snapshots
---------

      'preview-snapshot': (hash) ->
        await @vosco.isInstalled defer(error, isInstalled)
        unless isInstalled then process.exit 1
        hash = hash or 'HEAD'
        await @vosco.previewSnapshot hash, defer(error, diff)
        console.log diff

      'create-snapshot': (message) ->
        await @vosco.isInstalled defer(error, isInstalled)
        unless isInstalled then process.exit 1
        message = message or 'Untitled Snapshot'
        await @vosco.createSnapshot message, defer(error)

      'rollback-to-snapshot': (hash) ->
        await @vosco.isInstalled defer(error, isInstalled)
        unless isInstalled then process.exit 1
        await @vosco.rollbackToSnapshot hash, defer(error)

Branches
--------

      'get-branches': () ->
        await @vosco.isInstalled defer(error, isInstalled)
        unless isInstalled then process.exit 1
        await @vosco.getBranches defer(error, branches, current)
        for branch in branches
          branch = if branch is current then "* #{branch}" else
            colors.xterm(240)("  #{branch}")
          console.log "#{branch}"

      'create-branch': (branch) ->
        await @vosco.isInstalled defer(error, isInstalled)
        unless isInstalled then process.exit 1
        await @vosco.createBranch branch, defer(error)

      'select-branch': (branch) ->
        await @vosco.isInstalled defer(error, isInstalled)
        unless isInstalled then process.exit 1
        await @vosco.selectBranch branch, defer(error)

      'delete-branch': (branch) ->
        await @vosco.isInstalled defer(error, isInstalled)
        unless isInstalled then process.exit 1
        await @vosco.deleteBranch branch, defer(error)

Export Module
-------------

    module.exports = (vosco) ->
      script.vosco = vosco
      return script

Dependencies
============

    _      = require "underscore"
    fs     = require "fs"
    path   = require "path"
    colors = require "cli-color"

VOSCO
=====

    script =

Setup
-----

      'install': () ->
        await @vosco.isInstalled defer(error, isInstalled)
        if isInstalled then process.exit 1
        await @vosco.install defer(error)

      'uninstall': () ->
        await @vosco.isInstalled defer(error, isInstalled)
        unless isInstalled then process.exit 1
        await @vosco.uninstall defer(error)

Repository
----------

      'get-status': () ->
        await @vosco.isInstalled defer(error, isInstalled)
        unless isInstalled then process.exit 1
        await @vosco.getStatus defer(error, status)
        getType = (file) -> file.type
        getPath = (file) -> colors.xterm(240)("  #{file.path}")
        groups  = _.groupBy status, getType
        for group, paths of groups
          console.log """
          #{group}
          #{paths.map(getPath).join("\n")}
          """

      'get-history': () ->
        await @vosco.isInstalled defer(error, isInstalled)
        unless isInstalled then process.exit 1
        await @vosco.getHistory defer(error, history)
        for commit in history
          hash = commit.hash.substr(0, 7)
          date = new Date(commit.date).toDateString()
          msg  = commit.message
          console.log colors.xterm(240)("  #{hash}  #{date}  #{msg}")

      'get-content-history': (path) ->
        await @vosco.isInstalled defer(error, isInstalled)
        unless isInstalled then process.exit 1
        # show warning if file doesn"t exist
        console.log "content history"

Snapshots
---------

      'preview-snapshot': (hash) ->
        await @vosco.isInstalled defer(error, isInstalled)
        unless isInstalled then process.exit 1
        hash = hash or 'HEAD'
        await @vosco.previewSnapshot hash, defer(error, diff)
        console.log diff

      'create-snapshot': (message) ->
        await @vosco.isInstalled defer(error, isInstalled)
        unless isInstalled then process.exit 1
        message = message or 'Untitled Snapshot'
        await @vosco.createSnapshot message, defer(error)

      'rollback-to-snapshot': (hash) ->
        await @vosco.isInstalled defer(error, isInstalled)
        unless isInstalled then process.exit 1
        await @vosco.rollbackToSnapshot hash, defer(error)

Branches
--------

      'get-branches': () ->
        await @vosco.isInstalled defer(error, isInstalled)
        unless isInstalled then process.exit 1
        await @vosco.getBranches defer(error, branches, current)
        for branch in branches
          branch = if branch is current then "* #{branch}" else
            colors.xterm(240)("  #{branch}")
          console.log "#{branch}"

      'create-branch': (branch) ->
        await @vosco.isInstalled defer(error, isInstalled)
        unless isInstalled then process.exit 1
        console.log "create new branch"

      'select-branch': (branch) ->
        await @vosco.isInstalled defer(error, isInstalled)
        unless isInstalled then process.exit 1
        console.log "select branch"

      'delete-branch': (branch) ->
        await @vosco.isInstalled defer(error, isInstalled)
        unless isInstalled then process.exit 1
        console.log "delete branch"

Export Module
-------------

    module.exports = (vosco) ->
      script.vosco = vosco
      return script


Made pipeline an array, removed levels default, moved help

    module.exports = require 'yargs'
      .usage 'Usage: $0 [options] name1 name2 ...'
      .example '$0',
        'Creates a 50 x 15 level with 16 x 14 cells and an auto-generated name'
      .example '$0 -vvvvvvvv',
        "Create a 50 x 15 level with 16 x 14 cells, an auto-generated name, and
        logging set to verbosity level 8 (max)"
      .example '$0 -w 20 -h 20',
        'Creates a 50 x 15 level with 40 X 40 cells & an auto-generated name'
      .example '$0 -w 10 -h 10 -x 4',
        'Creates a 50 x 15 level with 40 x 40 cells and an auto-generated name'
      .example '$0 lvl1 lvl2',
        'Creates two levels, lvl1 & lvl2, each 50 X 15 with 16 x 14 cells'
      .count 'verbosity'
      .alias 'v', 'verbosity'
      .describe 'v', 'Level of logging verbosity: 1 - 8'
      .alias 'r', 'rows'
      .describe 'r', 'Number of rows for map'
      .alias 'c', 'columns'
      .describe 'c', 'Number of columns for map'
      .alias 'w', 'width'
      .describe 'w', 'Width of a grid cell'
      .alias 'h', 'height'
      .describe 'h', 'Height of a grid cell'
      .alias 'x', 'multiplier'
      .describe 'x', 'Increase cell size by x times'
      .number 'l'
      .describe 'l', 'Number of auto-named levels to create'
      .array 'levels'
      .describe 'levels', 'Array of named levels to create'
      .array 'pipeline'
      .alias 'p', 'pipeline'
      .describe 'p', 'The steps of the render pipleline'
      .conflicts 'levels', 'l'
      .help 'help'
      .default
        verbosity:  3
        rows:       15
        columns:    50
        width:      8
        height:     7
        multiplier: 4
        pipeline:   [ 'png_blocks', 'png_walls', 'png_step_count' ]
      .epilogue '© Bryan Maynard 2016'
      .argv

    module.exports = require 'yargs'
      .usage 'Usage: $0 [options] name1 name2 ...'
      .example '$0',
        'Creates a 50 x 15 level with 16 x 14 cells and an auto-generated name'
      .example '$0 -vvvvvvvv',
        "Create a 50 x 15 level with 16 x 14 cells, an auto-generated name, and
        logging set to verbosity level 8 (max)"
      .example '$0 -w 20 -h 20',
        'Creates a 50 x 15 level with 40 X 40 cells & an auto-generated name'
      .example '$0 -w 10 -h 10 -x 4',
        'Creates a 50 x 15 level with 40 x 40 cells and an auto-generated name'
      .example '$0 lvl1 lvl2',
        'Creates two levels, lvl1 & lvl2, each 50 X 15 with 16 x 14 cells'
      .count 'verbosity'
      .alias 'v', 'verbosity'
      .describe 'v', 'Level of logging verbosity: 1 - 8'
      .alias 'r', 'rows'
      .describe 'r', 'Number of rows for map'
      .alias 'c', 'columns'
      .describe 'c', 'Number of columns for map'
      .alias 'w', 'width'
      .describe 'w', 'Width of a grid cell'
      .alias 'h', 'height'
      .describe 'h', 'Height of a grid cell'
      .alias 'x', 'multiplier'
      .describe 'x', 'Increase cell size by x times'
      .number 'l'
      .describe 'l', 'Number of auto-named levels to create'
      .array 'levels'
      .describe 'levels', 'Array of named levels to create'
      .alias 'p', 'pipeline'
      .describe 'p', 'The steps of the render pipleline'
      .conflicts 'levels', 'l'
      .default
        verbosity:  3
        rows:       15
        columns:    50
        width:      8
        height:     7
        multiplier: 4
        levels:     1
        pipeline:   'png_blocks,png_walls,png_step_count'
      .help 'help'
      .epilogue '© Bryan Maynard 2016'
      .argv