fill out api docs

fill out api docs

# Batman.StorageAdapter

`Batman.StorageAdapter`s handle persistence of [`Batman.Model`s](/docs/api/batman.model.html). Any `Batman.Model` which will be created, read, updated or deleted must have a storage adapter, which is declared with [`Batman.Model@persist`](/docs/api/batman.model.html#class_function_persist):

```coffeescript
class App.Superhero extends Batman.Model
  @persist Batman.RestStorage # a StorageAdapter subclass
```

__Protip:__ Consider setting the storage adapter in one `Batman.Model` subclass, then extending that subclass in the rest of your app. Then, you won't have to call `@persist` every time:

```coffeescript
class App.Model extends Batman.Model
  @persist Batman.LocalStorage

class App.Superhero extends App.Model
  # no need to call @persist here!

class App.Sidekick extends App.Model
  # or here!
```

### Batman's Included StorageAdapters

Batman ships with a few storage adapters to get you up and coding quickly:

- `Batman.LocalStorage` uses [`window.localStorage`](https://developer.mozilla.org/en-US/docs/Web/Guide/API/DOM/Storage#localStorage) to persist records.
- `Batman.SessionStorage` extends `Batman.LocalStorage` and uses [`window.sessionStorage`](https://developer.mozilla.org/en-US/docs/Web/Guide/API/DOM/Storage#sessionStorage) to persist records.
- `Batman.RestStorage` uses [HTTP REST](http://en.wikipedia.org/wiki/REST) to persist records, mapping HTTP verbs to storage operations and handling HTTP response codes appropriately. _Note: Because `Batman.RestStorage` depends on [`Batman.Request`](/docs/api/batman.request.html), you'll need a [platform library](/docs/api/batman.request.html#platform_request_implmentation_libraries) to implement `Batman.Request`._

### Batman.RailsStorage
Also, `Batman.RailsStorage` (available in separate file: [Coffee](https://github.com/batmanjs/batman/blob/master/src/extras/batman.rails.coffee), JS) extends `Batman.RestStorage` and provides some helpful Rails integrations:

- If the response's status code is 422 (`Unprocessable Entity`), Any `errors` defined in the JSON response will be added to the `Batman.Model`'s [errors](/docs/api/batman.model.html#prototype_accessor_errors).
- By default, Batman will get the Rails CSRF token from the `<head>`and send it as a request header. (This can be prevented by setting `Batman.config.protectFromCSRF = false`.)
- If a record's URL doesn't end in `.json` and doesn't include a query string, `.json` is automatically appended to the URL.

_Note: Like `Batman.RestStorage`, `Batman.RailsStorage` depends on [`Batman.Request`](/docs/api/batman.request.html), so you'll need a [platform library](/docs/api/batman.request.html#platform_request_implmentation_libraries) to implement `Batman.Request`._

## Subclassing Batman.StorageAdapter

You may want to customize Batman's storage operations for your own app, for example:

- Adding before- and after-operation callbacks
- Overriding default storage operations

To do this, extend `Batman.StorageAdapter` (or one of the provided subclasses), then use your storage adapter to persist your models.
```
class App.HeroicStorageAdapter extends Batman.LocalStorage
  # filters, overrides

class App.Superhero extends Batman.Model
  @persist App.HeroicStorageAdapter
```
### `env` and `next`

Storage operations and callbacks each take two arguments: `env` and `next`.

`env` is a vanilla JS object which is passed to each function in the chain. `Batman.StorageAdapter` sets these attributes on `env`:

- `env.subject` is the `Batman.Model` record which had the storage operation called on it.
- `env.options` is an object containing the options passed when the operations was called on the subject.
- `env.action` is the storage operation that will be executed by the storage adapter.
- `env.error` stores any errors that occur during the chain. It becomes the first argument to them model's callback.
- `env.result` must be set by the `Batman.StorageAdapter` subclass after the operation is complete. It is a copy of the record and it becomes the second argument to the model's callback.

Storage adapters may use `env` to gather information and use it in its operations. For example, `Batman.RestStorage` adds some attributes to `env` and `env.options`:

- `env.request` is the `Batman.Request` which implements the storage operation.
- `env.options.method` is the HTTP method used by `Batman.Request` to implement the storage operation.
- `env.options.url` is the URL used by `Batman.Request` to implement the storage operation.

`next` is a reference to the next operation in the call chain. Any operation must call `next()`, otherwise the call chain will not continue. To ensure the completion of the call chain, consider wrapping your function in [`@nextIfError`](/docs/api/batman.storageadapter.html#class_function_nextiferror).

### Adding Callbacks

Callbacks can be registered with [`before`](/docs/api/batman.storageadapter.html#prototype_function_before) and [`after`](/docs/api/batman.storageadapter.html#prototype_function_after) on any storage operation (listed below) or `'all'` (which runs the callback before or after any operation). Callbacks accept two arguments, `env` and `next`, discussed below.

```coffeescript
class App.SpecificHeaderStorageAdapter extends Batman.RestStorage
  # include a specific header in all requests:
  @::before 'all', (env, next) ->
    headers = env.options.headers || {}
    headers["App-Specific-Header"] = App.getSpecificHeader()
    next()

  @::after 'create', (env, next) ->
    console.log "A #{env.subject.constructor.name} was created!"
    next()
```

### Storage Operations

Any `StorageAdapter` must implement these operations as instance methods, for example:
```
class App.ModifiedStorageAdapter extends Batman.RestStorage
  destroy: (env, next) ->
    # your custom destroy operation
    next()
```

## ::create(env : Object, next : Function)

Saves a new record. When `save` is called on a record and `isNew` returns `true`, the storage adapter's `create` method is called.

## ::read(env : Object, next : Function)

Loads a record from storage into a Batman app.

## ::update(env : Object, next : Function)

Updates an existing record. When `save` is called on a record and `isNew` returns `false`, the storage adapter's `save` method is called.

## ::destroy(env : Object, next : Function)

Destroys an existing record.

## ::readAll(env : Object, next : Function)

Loads all instances of a `Batman.Model` from storage into memory.

### Other useful methods

## @nextIfError(wrappedFunction : Function )

Wraps functions in the call chain, bypassing the function body and calling `next()` if `env.error?`
Otherwise, calls the wrapped function with `env` and `next`.

    test "StorageAdapter@skipIfError doesn't call the function if env.error?", ->
      insideFunction = createSpy()
      nextFunction = createSpy()

      functionWithWrapper = Batman.LocalStorage.skipIfError (env, next) ->
        insideFunction()

      dummyEnv = {error: true}

      functionWithWrapper(dummyEnv, nextFunction)

      equal dummyEnv.error?, true, 'an error is present'
      equal insideFunction.called, false, 'insideFunction was skipped'
      equal nextFunction.called, true, 'nextFunction was called'

Note that inside a storage adapter definition, this function is referenced as `@skipIfError`, for example:

```
class App.SpecialStorageAdapter extends Batman.RestStorage
  @::before 'create', @skipIfError (env, next) ->
    console.log("This will be skipped if env.error? is true!")
    next()
```

## ::before (keys... : Strings, filter : Function)

Registers `filter` as a before-operation callback for the storage operations named by `keys...`.

## ::after (keys... : Strings, filter : Function)

Registers `filter` as a after-operation callback for the storage operations named by `keys...`.

# Batman.StorageAdapter

`Batman.StorageAdapter`s handle persistence of [`Batman.Model`s](/docs/api/batman.model.html). Any `Batman.Model` which will be persisted must have a storage adapter, which is declared with [`Batman.Model@persist`](/docs/api/batman.model.html#class_function_persist):

```coffeescript
class App.Superhero extends Batman.Model
  @persist Batman.RestStorage # a StorageAdapter subclass
```

__Protip:__ Consider setting the storage adapter in one `Batman.Model` subclass, then extending that subclass in the rest of your app. Then, you won't have to call `@persist` every time:

```coffeescript
class App.Model extends Batman.Model
  @persist Batman.LocalStorage

class App.Superhero extends App.Model
  # no need to call @persist here!

class App.Sidekick extends App.Model
  # or here!
```

### Batman's Included StorageAdapters

Batman ships with a few storage adapters to help you get up and coding quickly:

- `Batman.LocalStorage` uses `window.localStorage` to persist records.
- `Batman.SessionStorage` extends `Batman.LocalStorage` and uses `window.sessionStorage` to persist records.
- `Batman.RestStorage` uses [HTTP REST](http://en.wikipedia.org/wiki/REST) to persist records, mapping HTTP verbs to storage operations and handling HTTP response codes appropriately. _Note: Because `Batman.RestStorage` depends on [`Batman.Request`](/docs/api/batman.request.html), you'll need a [platform library](/docs/api/batman.request.html#platform_request_implmentation_libraries) to implement `Batman.Request`._

### Batman.RailsStorage
Also, `Batman.RailsStorage` (available in separate file: [Coffee](https://github.com/batmanjs/batman/blob/master/src/extras/batman.rails.coffee), [JS](#)) extends `Batman.RestStorage` and provides some helpful Rails integrations:

- If the response's status code is 422 (`Unprocessable Entity`), Any `errors` defined in the JSON response will be added to the `Batman.Model`'s [errors](/docs/api/batman.model.html#prototype_accessor_errors).
- By default, Batman will get the Rails CSRF token from the `<head>`and send it as a request header. (This can be prevented by setting `Batman.config.protectFromCSRF = false`.)
- If a record's URL doesn't end in `.json` and doesn't include a query string, `.json` is automatically appended to the URL.

_Note: Like `Batman.RestStorage`, `Batman.RailsStorage` depends on [`Batman.Request`](/docs/api/batman.request.html), so you'll need a [platform library](/docs/api/batman.request.html#platform_request_implmentation_libraries) to implement `Batman.Request`._

## Subclassing Batman.StorageAdapter

You may want to customize Batman's storage operations for your own app, for example:

- Adding before- and after-operation callbacks
- Overriding default storage operations

To do this, extend `Batman.StorageAdapter` (or one of the provided subclasses), then use your storage adapter to persist your models.
```
class HeroicStorageAdapter extends Batman.LocalStorage
  # filters, overrides

class App.Superhero extends Batman.Model
  @persist App.HeroicStorageAdapter
```
### `env` and `next`

Storage operations and callbacks each take two arguments: `env` and `next`.

`env` in a vanilla JS object which is passed to each function in the chain. `Batman.StorageAdapter` expects at least two attributes:

- `env.subject` is the `Batman.Model` record which had the storage operation called on it.
- `env.options` is an object containing the options passed when the operations was called on the subject.

        test "env.subject and env.options are present in storage adapter calls", ->
          spiedEnvSubject = false
          spiedEnvOptions = false

          class SpyStorageAdapter extends TestStorageAdapter
            @::before 'create', 'update' (env, next) ->
              console.log "Spying!", env, next
              spiedEnvSubject = env.subject
              spiedEnvOptions = env.options
              next()

          class SpyModel extends Batman.Model
            @persist SpyStorageAdapter

          @spy = new SpyModel
          saveOptions = {someKey: "someValue"}
          @spy.save saveOptions, (err, savedSpy) ->
            throw err if err

          console.log @spy, spiedEnvSubject
          console.log saveOptions, spiedEnvOptions

          equal undefined, spiedEnvSubject.message
          equal spiedEnvSubject, @spy.toJSON()
          equal spiedEnvOptions, saveOptions

`next` is a reference to the next operation in the call chain. Any operation must call `next()`, otherwise the call chain will fail.

### Adding Callbacks

Callbacks can be registered on any storage operation (listed below). Callbacks accept

```coffeescript
class App.SpecificHeaderStorageAdapter extends Batman.RestStorage
  # include a specific header in all requests:
  @::before 'all' (env, next) ->
    headers = env.options.headers || {}
    headers["App-Specific-Header"] = App.getSpecificHeader()
    next()

  @::after 'create', (env, next) ->
    console.log "A #{env.subject.constructor.name} was created!"
    next()
```

### Storage operations

Any `StorageAdapter` must implement these operations as instance methods, for example:
```
class App.ModifiedStorageAdapter extends Batman.RestStorage
  destroy: (env, next) ->
    # your alternative destroy operation
    next()
```

## ::create(env : Object, next : Function)

When `save` is called on a record and `isNew` returns

## ::read(env : Object, next : Function)

## ::update(env : Object, next : Function)

## ::destroy(env : Object, next : Function)

## ::readAll(env : Object, next : Function)

### Other useful methods

## @nextIfError(wrappedFunction : Function )

Wraps functions in the call chain, bypassing the function body and calling `next()` if `env.error?`
Otherwise, calls the wrapped function with `env` and `next`.

    test "StorageAdapter@skipIfError doesn't call the function if env.error?", ->
      insideFunctionWasCalled = false
      nextFunctionWasCalled = false

      insideFunction = ->
        insideFunctionWasCalled = true

      nextFunction = ->
        nextFunctionWasCalled = true

      functionWithWrapper = Batman.LocalStorage.skipIfError (env, next) ->
        insideFunction() # would set insideFunctionWasCalled to true

      dummyEnv = {error: true}

      functionWithWrapper(dummyEnv, nextFunction)

      equal nextFunctionWasCalled, true
      equal insideFunctionWasCalled, false


add a horizontal rule

add a horizontal rule

Policies
--------
This is the glue-file that manages how user-policies (e.g. who's allowed to do what) get defined.  This file is responsible for grabbing the

Load in the required libraries

    _ = require 'lodash'
    Promise = require "bluebird"

Load up the user-defined policies.

    policies = require '../config/policies'

Start the function.  We're expecting an array of objects with the fields `actionName` and `controllerName`, and the node `req` and `res` objects just in case the policies need them.

    module.exports = (routeActions, req, res) ->

We want to reject or resolve the promise based on whether or not someone is allowed to do something.  Therefore, it'd be smart to wrap this in a promise.

        new Promise (resolve, reject) ->

Let's load up the helper function that grabs all the policy files.  This function was arguably useful in its own right, so it made sense to make it its own separate module.

            require('./getPolicies')()

Once the policies are done loading, we can move on.

            .then (actualPolicies) ->

Since there is an array routeActions (purposefully), we need to loop through them, and since we need the result afterwards, we can use a map.  Also, since there is a small risk of a couple nested arrays (due to the way that the policies have been implemented, lets flatten it after we're done.

                policyResults = _.flatten _.map routeActions, (action) ->

*****

What I'm doing here is looking for the policies file.
If there is a policy defined for that action, use that, else use the * catch all.

The flatten is necessary simply because I don't want to punish people for only listing one policy outside of an array.  Subsequently, we'll wrap everything in an array and then flatten it.

                        tempPolicies = _.flatten [policies[action.controllerName][action.actionName] || policies[action.controllerName]['*'] ]

This is pretty straightforward; we'll loop up the policy in the hash
table and then return back their result (which can just be a true or false

We want to make sure this is properly wrapped in promisey stuff, so we can init
an empty promise then run our stuff in the "then". Since our empty promise only
resolves, it won't hit the catch.

                        _.map tempPolicies, (policy) ->
                            tempPromise = new Promise (resolve, reject) -> do resolve
                            tempPromise.then -> actualPolicies[policy] req

Once all the policy promises have resolved, let's return out response.

                Promise.all policyResults
                .then (results) ->
                    # Not entirely sure I like this logic, but it's pretty straightforward:
                    # I want to allow synchronous or asynchronous policies.  Subsequently, we can
                    # either allow the policy to reject if not permitted or to simply return false.
                    unless false in results
                      resolve true
                    else
                      reject "Not Authorized"

Policies
--------
This is the glue-file that manages how user-policies (e.g. who's allowed to do what) get defined.  This file is responsible for grabbing the

Load in the required libraries

    _ = require 'lodash'
    Promise = require "bluebird"

Load up the user-defined policies.

    policies = require '../config/policies'

Start the function.  We're expecting an array of objects with the fields `actionName` and `controllerName`, and the node `req` and `res` objects just in case the policies need them.

    module.exports = (routeActions, req, res) ->

We want to reject or resolve the promise based on whether or not someone is allowed to do something.  Therefore, it'd be smart to wrap this in a promise.

        new Promise (resolve, reject) ->

Let's load up the helper function that grabs all the policy files.  This function was arguably useful in its own right, so it made sense to make it its own separate module.

            require('./getPolicies')()

Once the policies are done loading, we can move on.

            .then (actualPolicies) ->

Since there is an array routeActions (purposefully), we need to loop through them, and since we need the result afterwards, we can use a map.  Also, since there is a small risk of a couple nested arrays (due to the way that the policies have been implemented, lets flatten it after we're done.

                policyResults = _.flatten _.map routeActions, (action) ->

What I'm doing here is looking for the policies file.
If there is a policy defined for that action, use that, else use the * catch all.

The flatten is necessary simply because I don't want to punish people for only listing one policy outside of an array.  Subsequently, we'll wrap everything in an array and then flatten it.

                        tempPolicies = _.flatten [policies[action.controllerName][action.actionName] || policies[action.controllerName]['*'] ]

This is pretty straightforward; we'll loop up the policy in the hash
table and then return back their result (which can just be a true or false

We want to make sure this is properly wrapped in promisey stuff, so we can init
an empty promise then run our stuff in the "then". Since our empty promise only
resolves, it won't hit the catch.

                        _.map tempPolicies, (policy) ->
                            tempPromise = new Promise (resolve, reject) -> do resolve
                            tempPromise.then -> actualPolicies[policy] req

Once all the policy promises have resolved, let's return out response.

                Promise.all policyResults
                .then (results) ->
                    # Not entirely sure I like this logic, but it's pretty straightforward:
                    # I want to allow synchronous or asynchronous policies.  Subsequently, we can
                    # either allow the policy to reject if not permitted or to simply return false.
                    unless false in results
                      resolve true
                    else
                      reject "Not Authorized"


Add getContentHistory

Add getContentHistory

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

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

VOSCO
=====

    voscoCLI = {}

Setup
-----

    voscoCLI.install = (callback) ->
      await @original.isInstalled defer(error, isInstalled)
      if isInstalled then process.exit 1
      await @original.install defer(error)
      callback null

    voscoCLI.uninstall = (callback) ->
      await @original.isInstalled defer(error, isInstalled)
      unless isInstalled then process.exit 1
      await @original.uninstall defer(error)
      callback null

    voscoCLI.isInstalled = (callback) ->
      await @original.isInstalled defer(error, isInstalled)
      callback null, isInstalled

Repository
----------

    voscoCLI.getStatus = (callback) ->
      await @original.isInstalled defer(error, isInstalled)
      unless isInstalled then process.exit 1
      await @original.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")}
        """
      callback null

    voscoCLI.getHistory = (callback) ->
      await @original.isInstalled defer(error, isInstalled)
      unless isInstalled then process.exit 1
      await @original.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}")
      callback null

    voscoCLI.getContentHistory = (path, callback) ->
      await @original.isInstalled defer(error, isInstalled)
      unless isInstalled then process.exit 1
      await @original.getContentHistory path, defer(error, history)
      console.log history
      callback null

Snapshots
---------

    voscoCLI.previewSnapshot = (hash, callback) ->
      await @original.isInstalled defer(error, isInstalled)
      unless isInstalled then process.exit 1
      hash = hash or 'HEAD'
      await @original.previewSnapshot hash, defer(error, diff)
      console.log diff
      callback null

    voscoCLI.createSnapshot = (message, callback) ->
      await @original.isInstalled defer(error, isInstalled)
      unless isInstalled then process.exit 1
      message = message or 'Untitled Snapshot'
      await @original.createSnapshot message, defer(error)
      callback null

    voscoCLI.rollbackToSnapshot = (hash, callback) ->
      await @original.isInstalled defer(error, isInstalled)
      unless isInstalled then process.exit 1
      await @original.rollbackToSnapshot hash, defer(error)
      callback null

Branches
--------

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

    voscoCLI.createBranch = (branch, callback) ->
      await @original.isInstalled defer(error, isInstalled)
      unless isInstalled then process.exit 1
      await @original.createBranch branch, defer(error)
      callback null

    voscoCLI.selectBranch = (branch, callback) ->
      await @original.isInstalled defer(error, isInstalled)
      unless isInstalled then process.exit 1
      await @original.selectBranch branch, defer(error)
      callback null

    voscoCLI.deleteBranch = (branch, callback) ->
      await @original.isInstalled defer(error, isInstalled)
      unless isInstalled then process.exit 1
      await @original.deleteBranch branch, defer(error)
      callback null

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

    module.exports = (vosco) ->
      voscoCLI.original = vosco
      return voscoCLI

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

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

VOSCO
=====

    voscoCLI = {}

Setup
-----

    voscoCLI.install = (callback) ->
      await @original.isInstalled defer(error, isInstalled)
      if isInstalled then process.exit 1
      await @original.install defer(error)
      callback null

    voscoCLI.uninstall = (callback) ->
      await @original.isInstalled defer(error, isInstalled)
      unless isInstalled then process.exit 1
      await @original.uninstall defer(error)
      callback null

    voscoCLI.isInstalled = (callback) ->
      await @original.isInstalled defer(error, isInstalled)
      callback null, isInstalled

Repository
----------

    voscoCLI.getStatus = (callback) ->
      await @original.isInstalled defer(error, isInstalled)
      unless isInstalled then process.exit 1
      await @original.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")}
        """
      callback null

    voscoCLI.getHistory = (callback) ->
      await @original.isInstalled defer(error, isInstalled)
      unless isInstalled then process.exit 1
      await @original.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}")
      callback null

    voscoCLI.getContentHistory = (path, callback) ->
      await @original.isInstalled defer(error, isInstalled)
      unless isInstalled then process.exit 1
      # show warning if file doesn"t exist
      console.log "content history for #{path}"
      callback null

Snapshots
---------

    voscoCLI.previewSnapshot = (hash, callback) ->
      await @original.isInstalled defer(error, isInstalled)
      unless isInstalled then process.exit 1
      hash = hash or 'HEAD'
      await @original.previewSnapshot hash, defer(error, diff)
      console.log diff
      callback null

    voscoCLI.createSnapshot = (message, callback) ->
      await @original.isInstalled defer(error, isInstalled)
      unless isInstalled then process.exit 1
      message = message or 'Untitled Snapshot'
      await @original.createSnapshot message, defer(error)
      callback null

    voscoCLI.rollbackToSnapshot = (hash, callback) ->
      await @original.isInstalled defer(error, isInstalled)
      unless isInstalled then process.exit 1
      await @original.rollbackToSnapshot hash, defer(error)
      callback null

Branches
--------

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

    voscoCLI.createBranch = (branch, callback) ->
      await @original.isInstalled defer(error, isInstalled)
      unless isInstalled then process.exit 1
      await @original.createBranch branch, defer(error)
      callback null

    voscoCLI.selectBranch = (branch, callback) ->
      await @original.isInstalled defer(error, isInstalled)
      unless isInstalled then process.exit 1
      await @original.selectBranch branch, defer(error)
      callback null

    voscoCLI.deleteBranch = (branch, callback) ->
      await @original.isInstalled defer(error, isInstalled)
      unless isInstalled then process.exit 1
      await @original.deleteBranch branch, defer(error)
      callback null

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

    module.exports = (vosco) ->
      voscoCLI.original = vosco
      return voscoCLI


mark invalid in validate

mark invalid in validate

Copyright 2014 - 2015
Author: Varuna Jayasiri http://blog.varunajayasiri.com

#Value

    Mod.require 'Models.Properties',
     'Models.Property.Base'
     'Models.Property.EditBase'
     'Weya'
     (PROPERTIES, Base, EditBase, Weya) ->

Class

      class Value extends Base
       @extend()

       propertyType: 'value'

       @default 'value', (str, stack, isData = false) ->
        if (typeof str) isnt 'string'
         throw new Error "Exected string: #{typeof str}, #{str}"
        return str

       @default 'string', (value, stack) -> "#{value}"

       @default 'rows', 1
       @default 'columns', 20

       @default 'valid', (str, stack, isData = false) ->
        if (typeof str) isnt 'string'
         return false
        else
         return true

       @default 'search', (str, stack) -> null

       @default 'default', -> ''

       @default 'isDefault', (value, stack) ->
        value is @schema.default()

       parse: (data, stack) ->
        r = super data, stack
        return r unless r is true
        if not @schema.valid data, stack, true
         return @error 'invalid'

        return {
         score: 1
         errors: []
         value: @schema.value data, stack, true
        }

       toJSON: (value, stack) -> value
       toJSONFull: (value, stack) -> value

       edit: (elem, value, onChanged, stack) ->
        new Edit this, elem, value, onChanged, stack

Edit

      class Edit extends EditBase
       @extend()

       @initialize (property, elem, value, onChanged, stack) ->
        @value = value
        @render()

       render: ->
        schema = @property.schema
        width = 2 + Math.round schema.columns * 7.25
        Weya elem: @elems.parent, context: this, ->
         if schema.rows is 1
          @$.elems.input = @input '.value',
           type: 'text'
           placeholder: @$.property._name
           style:
            width: "#{width}px"
         else
          @$.elems.input = @textarea '.value',
           rows: schema.rows
           columns: schema.columns
           style:
            width: "#{width}px"
            height: "#{Math.round schema.rows * 18}px"

         @$.elems.search = @div '.search',
          style:
           display: 'none'
           width: "#{width}px"

        @elems.input.value = schema.string @value, @stack
        @elems.input.addEventListener "input", @on.change
        @elems.input.addEventListener "focus", @on.focus
        @elems.input.addEventListener "blur", @on.blur
        @elems.search.addEventListener 'click', @on.searchClick
        @change()

       search: ->
        value = @elems.input.value
        results = @property.schema.search.call @property, value, @stack
        return if not results?
        return if not Array.isArray results

        if results.length is 0
         @elems.search.style.display = 'none'
         return

        @elems.search.style.display = 'block'
        @elems.search.innerHTML = ''
        Weya elem: @elems.search, ->
         for r, i in results
          break if i > 10
          e = @div r
          e._result = r

       @listen 'searchClick', (e) ->
        n = e.target
        result = null
        while n? and n isnt @elems.search
         if n._result
          result = n._result
          break
         n = n.parentNode

        return if not result?
        @elems.input.value = result
        @elems.search.style.display = 'none'
        @change()

       @listen 'focus', (e) -> @search()
       @listen 'blur', (e) ->
        setTimeout =>
         @elems.search.style.display = 'none'
        , 400

       @listen 'change', (e) ->
        @search()
        @change()

       change: ->
        value = @elems.input.value
        if not @property.schema.valid.call @property, value, @stack
         @elems.input.classList.add 'invalid'
        else
         @elems.input.classList.remove 'invalid'
         value = @property.schema.value.call this, value, @stack
         @onChanged value, true

       validate: ->
        value = @elems.input.value
        if not @property.schema.valid.call @property, value, @stack
         @elems.input.classList.add 'invalid'
         return false
        else
         @elems.input.classList.remove 'invalid'
         return true

       destroy: ->

      PROPERTIES.register 'value', Value

Copyright 2014 - 2015
Author: Varuna Jayasiri http://blog.varunajayasiri.com

#Value

    Mod.require 'Models.Properties',
     'Models.Property.Base'
     'Models.Property.EditBase'
     'Weya'
     (PROPERTIES, Base, EditBase, Weya) ->

Class

      class Value extends Base
       @extend()

       propertyType: 'value'

       @default 'value', (str, stack, isData = false) ->
        if (typeof str) isnt 'string'
         throw new Error "Exected string: #{typeof str}, #{str}"
        return str

       @default 'string', (value, stack) -> "#{value}"

       @default 'rows', 1
       @default 'columns', 20

       @default 'valid', (str, stack, isData = false) ->
        if (typeof str) isnt 'string'
         return false
        else
         return true

       @default 'search', (str, stack) -> null

       @default 'default', -> ''

       @default 'isDefault', (value, stack) ->
        value is @schema.default()

       parse: (data, stack) ->
        r = super data, stack
        return r unless r is true
        if not @schema.valid data, stack, true
         return @error 'invalid'

        return {
         score: 1
         errors: []
         value: @schema.value data, stack, true
        }

       toJSON: (value, stack) -> value
       toJSONFull: (value, stack) -> value

       edit: (elem, value, onChanged, stack) ->
        new Edit this, elem, value, onChanged, stack

Edit

      class Edit extends EditBase
       @extend()

       @initialize (property, elem, value, onChanged, stack) ->
        @value = value
        @render()

       render: ->
        schema = @property.schema
        width = 2 + Math.round schema.columns * 7.25
        Weya elem: @elems.parent, context: this, ->
         if schema.rows is 1
          @$.elems.input = @input '.value',
           type: 'text'
           placeholder: @$.property._name
           style:
            width: "#{width}px"
         else
          @$.elems.input = @textarea '.value',
           rows: schema.rows
           columns: schema.columns
           style:
            width: "#{width}px"
            height: "#{Math.round schema.rows * 18}px"

         @$.elems.search = @div '.search',
          style:
           display: 'none'
           width: "#{width}px"

        @elems.input.value = schema.string @value, @stack
        @elems.input.addEventListener "input", @on.change
        @elems.input.addEventListener "focus", @on.focus
        @elems.input.addEventListener "blur", @on.blur
        @elems.search.addEventListener 'click', @on.searchClick
        @change()

       search: ->
        value = @elems.input.value
        results = @property.schema.search.call @property, value, @stack
        return if not results?
        return if not Array.isArray results

        if results.length is 0
         @elems.search.style.display = 'none'
         return

        @elems.search.style.display = 'block'
        @elems.search.innerHTML = ''
        Weya elem: @elems.search, ->
         for r, i in results
          break if i > 10
          e = @div r
          e._result = r

       @listen 'searchClick', (e) ->
        n = e.target
        result = null
        while n? and n isnt @elems.search
         if n._result
          result = n._result
          break
         n = n.parentNode

        return if not result?
        @elems.input.value = result
        @elems.search.style.display = 'none'
        @change()

       @listen 'focus', (e) -> @search()
       @listen 'blur', (e) ->
        setTimeout =>
         @elems.search.style.display = 'none'
        , 400

       @listen 'change', (e) ->
        @search()
        @change()

       change: ->
        value = @elems.input.value
        if not @property.schema.valid.call @property, value, @stack
         @elems.input.classList.add 'invalid'
        else
         @elems.input.classList.remove 'invalid'
         value = @property.schema.value.call this, value, @stack
         @onChanged value, true

       validate: ->
        value = @elems.input.value
        if not @property.schema.valid.call @property, value, @stack
         return false
        else
         return true

       destroy: ->

      PROPERTIES.register 'value', Value


fix function syntax error for caching

fix function syntax error for caching

# Application Routes

The purpose of a route is to connect URL resources with underlying code.  I
also do explicit data normalization (input -> dictionary to pass to method/dict
from method -> view) at this stage.

To configure routes, the only object we need access to is `app`.  This module
can be called with `require("./lib/routes")(app)` as long as `app` is an
already initialized ExpressJS application.

Also note, app.models contians:

- **app.models.db** - Mongoose MongoDB connection
- **app.models.MODELNAME** - model objects

    module.exports = (app) ->

## Function cache(res, seconds)

Set cache headers on this response.

Usage: `cache(res,86400)`

    cache = (res,seconds) ->
      res.header 'Cache-control', 'Cache-Control:public, max-age='+seconds

## Function nocache(res)

Set uncacheable headers on this response

Usage: `nocache(res)`

    nocache = (res) ->
      res.header 'Cache-Control', 'no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0'

## GET /

Render the main index page.

      app.get '/', (req, res) ->
        cache res, 2592000
        res.redirect '/page/index'

## GET /page/:name

Render static pages at /view/page/name.html

      app.get '/page/:name', (req, res) ->
        app.models.page.get req.params.name, (err, page) ->
          if err
            console.error 'page '+req.params.name+' not found in db, showing 500'
            nocache res
            res.render 'error/500'
          else
            cache res, 86400
            res.render 'page/dynamic', page

## POST /page/_post

Post a page into the database

WARNING no security! .. then again, it doesn't read input at the moment either.
<!-- ' -->

      app.post '/page/_post', (req, res) ->
        nocache res
        sample =
          name: 'test'
          title: 'Test Page'
          content: 'Hello world, from inside the DB'
        app.models.page sample, (err, page) ->
          if err
            page =
              name: 'error'
              title: 'Error'
              content: 'There was an error posting your page, please try again.'
          res.render 'page/dynamic', page

## GET /login

Render static pages at /view/login/index.html

And never cache the login page, it breaks csrf

      app.get '/login', (req, res) ->
        nocache res
        res.render 'login'

## POST /login/_post

Do Login action, and redirect as appropriate.

      app.post '/login/_post', (req, res) ->
        nocache res
        require('../login').checkUser req.body.username, req.body.password, (user, securityLevel) ->
          console.info 'login callback = '+user+', '+securityLevel
          req.session.user = user
          req.session.securityLevel = securityLevel
          res.cookie 'nocache', '1'
          if securityLevel
            res.redirect '/'
          else
            res.redirect '/login'

## GET /login/clear

Logout from the website by clearing the session

      app.get '/login/clear', (req, res) ->
        nocache res
        req.session.destroy (err) ->
          res.clearCookie 'nocache'
          res.redirect '/login'

## GET /directory

Download PDF to browser

      app.get '/directory', (req, res) ->
        nocache res
        if req.session.securityLevel
          request = require 'request'
          request app.directory_url, (err, cbres, data) ->
            if err or cbres.statusCode != 200
              res.render 500, 'Error retrieving full directory'
            else
              dir = JSON.parse data
              page =
                name: 'Full Directory'
                title: 'Full Directory'
                directory: dir.data
                helpers:
                  month: (num) ->
                    return [null, 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'][num]
              res.render 'directory', page
        else
          res.redirect '/login'

## GET /directory/directory.pdf

Download PDF to browser

      app.get '/directory/directory.pdf', (req, res) ->
        nocache res
        if req.session.securityLevel
          res.download 'protected/directory.pdf'
        else
          res.redirect '/login'

## Copying

This software is released under the ISC License.

Copyright (c) 2013, Cameron King <cking@ecc12.com>

Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.

THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.

<!-- vim: ts=2 sts=2 sw=2 expandtab
     CoffeeScript-friendly tabstops in vim. -->

# Application Routes

The purpose of a route is to connect URL resources with underlying code.  I
also do explicit data normalization (input -> dictionary to pass to method/dict
from method -> view) at this stage.

To configure routes, the only object we need access to is `app`.  This module
can be called with `require("./lib/routes")(app)` as long as `app` is an
already initialized ExpressJS application.

Also note, app.models contians:

- **app.models.db** - Mongoose MongoDB connection
- **app.models.MODELNAME** - model objects

    module.exports = (app) ->

## Function cache(res, seconds)

Set cache headers on this response.

Usage: `cache(res,86400)`

    cache(res,seconds) ->
      res.header 'Cache-control', 'Cache-Control:public, max-age='+seconds

## Function nocache(res)

Set uncacheable headers on this response

Usage: `nocache(res)`

    nocache(res) ->
      res.header 'Cache-Control', 'no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0'

## GET /

Render the main index page.

      app.get '/', (req, res) ->
        cache res, 2592000
        res.redirect '/page/index'

## GET /page/:name

Render static pages at /view/page/name.html

      app.get '/page/:name', (req, res) ->
        app.models.page.get req.params.name, (err, page) ->
          if err
            console.error 'page '+req.params.name+' not found in db, showing 500'
            nocache res
            res.render 'error/500'
          else
            cache res, 86400
            res.render 'page/dynamic', page

## POST /page/_post

Post a page into the database

WARNING no security! .. then again, it doesn't read input at the moment either.
<!-- ' -->

      app.post '/page/_post', (req, res) ->
        nocache res
        sample =
          name: 'test'
          title: 'Test Page'
          content: 'Hello world, from inside the DB'
        app.models.page sample, (err, page) ->
          if err
            page =
              name: 'error'
              title: 'Error'
              content: 'There was an error posting your page, please try again.'
          res.render 'page/dynamic', page

## GET /login

Render static pages at /view/login/index.html

And never cache the login page, it breaks csrf

      app.get '/login', (req, res) ->
        nocache res
        res.render 'login'

## POST /login/_post

Do Login action, and redirect as appropriate.

      app.post '/login/_post', (req, res) ->
        nocache res
        require('../login').checkUser req.body.username, req.body.password, (user, securityLevel) ->
          console.info 'login callback = '+user+', '+securityLevel
          req.session.user = user
          req.session.securityLevel = securityLevel
          res.cookie 'nocache', '1'
          if securityLevel
            res.redirect '/'
          else
            res.redirect '/login'

## GET /login/clear

Logout from the website by clearing the session

      app.get '/login/clear', (req, res) ->
        nocache res
        req.session.destroy (err) ->
          res.clearCookie 'nocache'
          res.redirect '/login'

## GET /directory

Download PDF to browser

      app.get '/directory', (req, res) ->
        nocache res
        if req.session.securityLevel
          request = require 'request'
          request app.directory_url, (err, cbres, data) ->
            if err or cbres.statusCode != 200
              res.render 500, 'Error retrieving full directory'
            else
              dir = JSON.parse data
              page =
                name: 'Full Directory'
                title: 'Full Directory'
                directory: dir.data
                helpers:
                  month: (num) ->
                    return [null, 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'][num]
              res.render 'directory', page
        else
          res.redirect '/login'

## GET /directory/directory.pdf

Download PDF to browser

      app.get '/directory/directory.pdf', (req, res) ->
        nocache res
        if req.session.securityLevel
          res.download 'protected/directory.pdf'
        else
          res.redirect '/login'

## Copying

This software is released under the ISC License.

Copyright (c) 2013, Cameron King <cking@ecc12.com>

Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.

THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.

<!-- vim: ts=2 sts=2 sw=2 expandtab
     CoffeeScript-friendly tabstops in vim. -->


Fix match info display

Fix match info display

Match Info Manager
==================

We first set everything in a callback, so that it is invoked only after the
loading of the DOM.

    $ ->

Input
-----

We build the match info from the team DB, the scores, and the
current match.

        inputStream = Bacon.combineTemplate
            match: CurrentMatch
            scores: League.scores
            teams: Data.teams

Display Management
------------------

First, we bind up the current match ID display.

        inputStream.filter( (x) -> x.match? )
                   .map( (x) -> x.match.key )
                   .assign $('.match-id'), 'text'

For each zone, we bind up the relevant information points.

        for n in [0..3]
            do (n) ->
                ifTeams = inputStream.filter( (x) -> x.match? )
                                     .filter( (x) -> x.match.teams? )
                team = ifTeams.map( (x) -> x.teams[x.match.teams[n]] ?
                                           {name: x.match.teams[n]} )
                score = ifTeams.map( (x) -> x.scores[x.match.teams[n]] ? 0 )

The team name, college, points, and notes.

                team.map( (x) -> x.name  )
                    .assign $(".zone-#{n}-name"), 'text'
                team.map( (x) -> x.college ? x.name )
                    .assign $(".zone-#{n}-college"), 'text'
                team.map( (x) -> x.notes ? '' )
                    .assign $(".zone-#{n}-notes"), 'text'
                score.assign $(".zone-#{n}-points"), 'text'

Match Info Manager
==================

We first set everything in a callback, so that it is invoked only after the
loading of the DOM.

    $ ->

Input
-----

We build the match info from the team DB, the scores, and the
current match.

        inputStream = Bacon.combineTemplate
            match: CurrentMatch
            scores: League.scores
            teams: Data.teams

Display Management
------------------

First, we bind up the current match ID display.

        #inputStream.filter( (x) -> x.match? )
        #           .map( (x) -> x.match.key? )
        #           .assign $('.match-id'), 'text'
        inputStream.filter( (x) -> x.match? )
                   .filter( (x) -> x.match.key? )
                   .map( (x) -> x.teams )
                   .assign $('.match-id'), 'text'

For each zone, we bind up the relevant information points.

        for n in [0..3]
            #ifTeams = inputStream.filter( (x) -> x.match? )
            #                     .filter( (x) -> x.match.teams? )
            ifTeams = Bacon.never().toProperty()
            team = ifTeams.map( (x) -> x.teams[x.match.teams[n]] )
            score = ifTeams.map( (x) -> x.scores[x.match.teams[n]] )

The team name, college, points, and notes.

            team.map( (x) -> x.name )
                .assign $(".zone-#{n}-name"), 'text'
            team.map( (x) -> x.college )
                .assign $(".zone-#{n}-college"), 'text'
            team.map( (x) -> x.notes )
                .assign $(".zone-#{n}-notes"), 'text'
            score.assign $(".zone-#{n}-points"), 'text'


byteLength for content-length

byteLength for content-length

    _self = this

    if console?.log?
     LOG = console.log.bind console
    else
     LOG = -> null

    if console?.error?
     ERROR_LOG = console.error.bind console
    else
     ERROR_LOG = -> null

    POLL_TYPE =
     progress: true

##Response class

    class Response
     constructor: (data, port, options) ->
      @id = data.id
      @port = port
      @options = options
      @queue = []
      @fresh = true

     progress: (progress, data, callback) ->
      @queue.push
       method: 'progress'
       data: data
       options: {progress: progress}
       callback: callback
      @_handleQueue()

     success: (data) ->
      @queue.push method: 'success', data: data, options: {}
      @_handleQueue()

     fail: (data) ->
      @queue.push method: 'fail', data: data, options: {}
      @_handleQueue()

     setOptions: (options) ->
      @options = options
      @fresh = true
      @_handleQueue()

     _handleQueue: ->
      return unless @queue.length > 0

      d = @queue[0]

      if @port.isStreaming
       @port.respond this, d.method, d.data, d.options, @options, d.callback
       @queue.shift()
      else if @fresh
       @port.respond this, d.method, d.data, d.options, @options, d.callback
       @queue.shift()
       @fresh = false

##Call class

    class Call
     constructor: (@id, @method, @data, @callbacks, @options) -> null

     handle: (data, options) ->
      if not @callbacks[options.status]?
       return if options.status is 'progress'
       #Handled by caller
       throw new Error "No callback registered #{@method} #{options.status}"
      self = this
      setTimeout (-> self.callbacks[options.status] data, options), 0

      if POLL_TYPE[options.status]?
       return false
      else
       return true

##Port base class

    class Port
     constructor: ->
      @handlers = {}
      @callsCache = {}
      @callsCounter = 0
      @id = parseInt Math.random() * 1000
      @responses = {}
      @wrappers =
       send: []
       respond: []
       handleCall: []
       handleResponse: []

     isStreaming: true

     onCallError: (msg, options) ->
      for id, call of @callsCache
       if not call.callbacks.fail?
        ERROR_LOG 'fail callback not registered', call.method, call.data
       else
        call.callbacks.fail error: 'connectionError', msg: msg, options: options, {}

      @callsCache = {}

      @errorCallback msg, options

     onHandleError: (msg, data, options) ->
      @errorCallback msg, data
      response = new Response data, this, options
      response.fail msg

     errorCallback: (msg, options) ->
      ERROR_LOG msg, options

     wrap: (wrapper) ->
      for key, f of wrapper
       if @wrappers[key]?
        @wrappers[key].push f
       else
        this[key] = f

###Send RPC call

     send: (method, data, callbacks, options = {}) ->
      if (typeof callbacks) is 'function'
       callbacks =
        success: callbacks

      for f in @wrappers.send
       return unless f.apply this, [method, data, callbacks, options]

      params = @_createCall method, data, callbacks, options
      @_send params, callbacks

###Respond to a RPC call

     respond: (response, status, data, options = {}, portOptions = {}, callback = null) ->
      for f in @wrappers.respond
       return unless f.apply this, [response, status, data, options, portOptions]

      @_respond (@_createResponse response, status, data, options), portOptions, callback
      if not POLL_TYPE[status]?
       delete @responses[response.id]

###Create Call object
This is a private function

     _createCall: (method, data, callbacks, options) ->
      call = new Call "#{@id}-#{@callsCounter}", method, data, callbacks, options
      @callsCounter++
      @callsCache[call.id] = call

      params =
       type: 'call'
       id: call.id
       method: call.method
       data: call.data

      params[k] = v for k, v of options
      return params

###Create Response object

     _createResponse: (response, status, data, options) ->
      params =
       type: 'response'
       id: response.id
       status: status
       data: data

      params[k] = v for k, v of options

      return params

###Add handler

     on: (method, callback) ->
      @handlers[method] = callback

###Handle incoming message

     _handleMessage: (data, options) ->
      switch data.type
       when 'response'
        @_handleResponse data, options
       when 'call'
        @_handleCall data, options
       when 'poll'
        @_handlePoll data, options

     _handleCall: (data, options) ->
      for f in @wrappers.handleCall
       return unless f.apply this, arguments
      if not @handlers[data.method]?
       @onHandleError "Unknown method: #{data.method}", data, options
       return
      @responses[data.id] = new Response data, this, options
      @handlers[data.method] data.data, data, @responses[data.id]

     _handleResponse: (data, options) ->
      for f in @wrappers.handleResponse
       return unless f.apply this, arguments
      if not @callsCache[data.id]?
       #Cannot reply
       @errorCallback "Response without call: #{data.id}", data
       return
      try
       if @callsCache[data.id].handle data.data, data
        delete @callsCache[data.id]
      catch e
       @errorCallback e.message, data
       delete @callsCache[data.id]

     _handlePoll: (data, options) ->
      @onHandleError "Poll not implemented", data, options

##WorkerPort class
Used for browser and worker

    class WorkerPort extends Port
     constructor: (worker) ->
      super()
      @worker = worker
      @worker.onmessage = @_onMessage.bind this
      #@worker.onerror = @onCallError.bind this

     _send: (data) ->  @worker.postMessage data
     _respond: (data, options, callback) ->
      @worker.postMessage data
      callback?()

     _onMessage: (e) ->
      data = e.data
      @_handleMessage data

##SocketPort class

    class SocketPort extends Port
     constructor: (socket) ->
      super()
      @socket = socket
      @socket.on 'message', @_onMessage.bind this

     _send: (data) ->  @socket.emit 'message', data
     _respond: (data, options, callback) ->
      @worker.emit 'message', data
      callback?()

     _onMessage: (e) ->
      data = e.data
      @_handleMessage data

##ServerSocketPort class

    class ServerSocketPort extends Port
     constructor: (server) ->
      super()
      @server = server
      @server.on 'connection', @_onConnection.bind this

     _onConnection: (socket) ->
      new SocketPort handlers: @handlers, socket

##AJAX class

    class AjaxHttpPort extends Port
     constructor: (options) ->
      super()
      @host = options.host ? 'localhost'
      @port = options.port ? 80
      @path = options.path ? '/'

     isStreaming: false

     _onRequest: (xhr) ->
      return unless xhr.readyState is 4
      status = xhr.status
      if ((not status and xhr.responseText? and xhr.responseText != '') or
          (status >= 200 and status < 300) or
          (status is 304))
       try
        jsonData = JSON.parse xhr.responseText
       catch e
        @onCallError 'ParseError', e
        return

       @_handleMessage jsonData, xhr: xhr
      else
       @onCallError 'Cannot connect to server'

     _respond: (data, options, callback) ->
      @errorCallback 'AJAX cannot respond', data
      callback?()

     _send: (data) ->
      data = JSON.stringify data
      xhr = new XMLHttpRequest
      xhr.open 'POST', "http://#{@host}:#{@port}#{@path}"
      xhr.onreadystatechange = =>
       @_onRequest xhr
      xhr.setRequestHeader 'Accept', 'application/json'
      #xhr.setRequestHeader 'Content-Type', 'application/json'
      xhr.send data

     _handleResponse: (data, options) ->
      for f in @wrappers.handleResponse
       return unless f.apply this, arguments
      if not @callsCache[data.id]?
       @errorCallback "Response without call: #{data.id}", data
       return
      call = @callsCache[data.id]
      if not call.handle data.data, data
       params =
        type: 'poll'
        id: call.id
       params[k] = v for k, v of call.options
       @_send params

##NodeHttpPort class

    class NodeHttpPort extends Port
     constructor: (options, http) ->
      super()
      @host = options.host ? 'localhost'
      @port = options.port ? 80
      @path = options.path ? '/'
      @http = http
      @_createHttpOptions()

     isStreaming: false

     _createHttpOptions: ->
      @httpOptions =
       hostname: @host
       port: @port
       path: @path
       method: 'POST'
       agent: false
       headers:
        accept: 'application/json'
       'content-type': 'application/json'

     _onRequest: (res) ->
      data = ''
      #LOG 'STATUS: ' + res.statusCode
      #LOG 'HEADERS: ' + JSON.stringify res.headers
      res.setEncoding 'utf8'
      res.on 'data', (chunk) ->
       data += chunk
      #LOG 'result', res
      res.on 'end', =>
       try
        jsonData = JSON.parse data
       catch e
        @onCallError 'ParseError', e
        return

       @_handleMessage jsonData, response: res

     _respond: (data, options, callback) ->
      data = JSON.stringify data
      res = options.response
      res.setHeader 'content-length', Buffer.byteLength data, 'utf8'
      if callback?
       res.once 'finish', ->
        callback()
       res.once 'close', ->
        callback()

      res.write data
      res.end()

     _send: (data, callbacks) ->
      data = JSON.stringify data
      options = @httpOptions
      options.headers['content-length'] = Buffer.byteLength data, 'utf8'

      req = @http.request options, @_onRequest.bind this
      delete options.headers['content-length']
      req.on 'error', (e) ->
       callbacks.fail? e

      req.write data
      req.end()

     _handleResponse: (data, options) ->
      for f in @wrappers.handleResponse
       return unless f.apply this, arguments
      if not @callsCache[data.id]?
       @errorCallback "Response without call: #{data.id}", data
       return
      call = @callsCache[data.id]
      if not call.handle data.data, data
       params =
        type: 'poll'
        id: call.id
       params[k] = v for k, v of call.options
       @_send params, call.callbacks

##NodeHttpServerPort class

    class NodeHttpServerPort extends Port
     constructor: (options, http) ->
      super()
      @port = options.port
      @http = http

     isStreaming: false

     _onRequest: (req, res) ->
      data = ''
      res.setHeader 'content-type', 'application/json'

      req.on 'data', (chunk) ->
       data += chunk
      req.on 'end', =>
       try
        jsonData = JSON.parse data
       catch e
        @errorCallback 'ParseError', e
        return

       @_handleMessage jsonData, response: res

     _respond: (data, options, callback) ->
      data = JSON.stringify data
      res = options.response
      res.setHeader 'content-length', Buffer.byteLength data, 'utf8'
      if callback?
       res.once 'finish', ->
        callback()
       res.once 'close', ->
        callback()

      res.write data
      res.end()

     listen: ->
      @server = @http.createServer @_onRequest.bind this
      @server.listen @port

     _handlePoll: (data, options) ->
      for f in @wrappers.handleCall
       return unless f.apply this, arguments
      if not @responses[data.id]?
       @onHandleError "Poll without response: #{data.id}", data, options
       return
      @responses[data.id].setOptions options

#IO Module

    IO =
     addPort: (name, port) ->
      IO[name] = port

     ports:
      WorkerPort: WorkerPort
      SocketPort: SocketPort
      AjaxHttpPort: AjaxHttpPort
      NodeHttpPort: NodeHttpPort
      NodeHttpServerPort: NodeHttpServerPort
      ServerSocketPort: ServerSocketPort

     Helpers:
      progress: (from, to, func) ->
       (progress, data) ->
        func? progress * (to - from) + from, data

     setup_____: (options) ->
      options.workers ?= []
      switch options.platform
       when 'ui'
        for worker in options.workers
         IO.addPort worker.name, new WorkerPort worker.js
        for socket in options.sockets
         IO.addPort socket.name, new SocketPort socket.url
       when 'node'
        for socket in options.listenSockets
         IO.addPort socket.name, new SocketListenPort socket.port
        for socket in options.sockets
         IO.addPort socket.name, new SocketPort socket.url
       when 'worker'
        IO.addPort 'UI', new WorkerListenPort _self

    if exports?
     module.exports = IO
    else
     _self.IO = IO

    _self = this

    if console?.log?
     LOG = console.log.bind console
    else
     LOG = -> null

    if console?.error?
     ERROR_LOG = console.error.bind console
    else
     ERROR_LOG = -> null

    POLL_TYPE =
     progress: true

##Response class

    class Response
     constructor: (data, port, options) ->
      @id = data.id
      @port = port
      @options = options
      @queue = []
      @fresh = true

     progress: (progress, data, callback) ->
      @queue.push
       method: 'progress'
       data: data
       options: {progress: progress}
       callback: callback
      @_handleQueue()

     success: (data) ->
      @queue.push method: 'success', data: data, options: {}
      @_handleQueue()

     fail: (data) ->
      @queue.push method: 'fail', data: data, options: {}
      @_handleQueue()

     setOptions: (options) ->
      @options = options
      @fresh = true
      @_handleQueue()

     _handleQueue: ->
      return unless @queue.length > 0

      d = @queue[0]

      if @port.isStreaming
       @port.respond this, d.method, d.data, d.options, @options, d.callback
       @queue.shift()
      else if @fresh
       @port.respond this, d.method, d.data, d.options, @options, d.callback
       @queue.shift()
       @fresh = false

##Call class

    class Call
     constructor: (@id, @method, @data, @callbacks, @options) -> null

     handle: (data, options) ->
      if not @callbacks[options.status]?
       return if options.status is 'progress'
       #Handled by caller
       throw new Error "No callback registered #{@method} #{options.status}"
      self = this
      setTimeout (-> self.callbacks[options.status] data, options), 0

      if POLL_TYPE[options.status]?
       return false
      else
       return true

##Port base class

    class Port
     constructor: ->
      @handlers = {}
      @callsCache = {}
      @callsCounter = 0
      @id = parseInt Math.random() * 1000
      @responses = {}
      @wrappers =
       send: []
       respond: []
       handleCall: []
       handleResponse: []

     isStreaming: true

     onCallError: (msg, options) ->
      for id, call of @callsCache
       if not call.callbacks.fail?
        ERROR_LOG 'fail callback not registered', call.method, call.data
       else
        call.callbacks.fail error: 'connectionError', msg: msg, options: options, {}

      @callsCache = {}

      @errorCallback msg, options

     onHandleError: (msg, data, options) ->
      @errorCallback msg, data
      response = new Response data, this, options
      response.fail msg

     errorCallback: (msg, options) ->
      ERROR_LOG msg, options

     wrap: (wrapper) ->
      for key, f of wrapper
       if @wrappers[key]?
        @wrappers[key].push f
       else
        this[key] = f

###Send RPC call

     send: (method, data, callbacks, options = {}) ->
      if (typeof callbacks) is 'function'
       callbacks =
        success: callbacks

      for f in @wrappers.send
       return unless f.apply this, [method, data, callbacks, options]

      params = @_createCall method, data, callbacks, options
      @_send params, callbacks

###Respond to a RPC call

     respond: (response, status, data, options = {}, portOptions = {}, callback = null) ->
      for f in @wrappers.respond
       return unless f.apply this, [response, status, data, options, portOptions]

      @_respond (@_createResponse response, status, data, options), portOptions, callback
      if not POLL_TYPE[status]?
       delete @responses[response.id]

###Create Call object
This is a private function

     _createCall: (method, data, callbacks, options) ->
      call = new Call "#{@id}-#{@callsCounter}", method, data, callbacks, options
      @callsCounter++
      @callsCache[call.id] = call

      params =
       type: 'call'
       id: call.id
       method: call.method
       data: call.data

      params[k] = v for k, v of options
      return params

###Create Response object

     _createResponse: (response, status, data, options) ->
      params =
       type: 'response'
       id: response.id
       status: status
       data: data

      params[k] = v for k, v of options

      return params

###Add handler

     on: (method, callback) ->
      @handlers[method] = callback

###Handle incoming message

     _handleMessage: (data, options) ->
      switch data.type
       when 'response'
        @_handleResponse data, options
       when 'call'
        @_handleCall data, options
       when 'poll'
        @_handlePoll data, options

     _handleCall: (data, options) ->
      for f in @wrappers.handleCall
       return unless f.apply this, arguments
      if not @handlers[data.method]?
       @onHandleError "Unknown method: #{data.method}", data, options
       return
      @responses[data.id] = new Response data, this, options
      @handlers[data.method] data.data, data, @responses[data.id]

     _handleResponse: (data, options) ->
      for f in @wrappers.handleResponse
       return unless f.apply this, arguments
      if not @callsCache[data.id]?
       #Cannot reply
       @errorCallback "Response without call: #{data.id}", data
       return
      try
       if @callsCache[data.id].handle data.data, data
        delete @callsCache[data.id]
      catch e
       @errorCallback e.message, data
       delete @callsCache[data.id]

     _handlePoll: (data, options) ->
      @onHandleError "Poll not implemented", data, options

##WorkerPort class
Used for browser and worker

    class WorkerPort extends Port
     constructor: (worker) ->
      super()
      @worker = worker
      @worker.onmessage = @_onMessage.bind this
      #@worker.onerror = @onCallError.bind this

     _send: (data) ->  @worker.postMessage data
     _respond: (data, options, callback) ->
      @worker.postMessage data
      callback?()

     _onMessage: (e) ->
      data = e.data
      @_handleMessage data

##SocketPort class

    class SocketPort extends Port
     constructor: (socket) ->
      super()
      @socket = socket
      @socket.on 'message', @_onMessage.bind this

     _send: (data) ->  @socket.emit 'message', data
     _respond: (data, options, callback) ->
      @worker.emit 'message', data
      callback?()

     _onMessage: (e) ->
      data = e.data
      @_handleMessage data

##ServerSocketPort class

    class ServerSocketPort extends Port
     constructor: (server) ->
      super()
      @server = server
      @server.on 'connection', @_onConnection.bind this

     _onConnection: (socket) ->
      new SocketPort handlers: @handlers, socket

##AJAX class

    class AjaxHttpPort extends Port
     constructor: (options) ->
      super()
      @host = options.host ? 'localhost'
      @port = options.port ? 80
      @path = options.path ? '/'

     isStreaming: false

     _onRequest: (xhr) ->
      return unless xhr.readyState is 4
      status = xhr.status
      if ((not status and xhr.responseText? and xhr.responseText != '') or
          (status >= 200 and status < 300) or
          (status is 304))
       try
        jsonData = JSON.parse xhr.responseText
       catch e
        @onCallError 'ParseError', e
        return

       @_handleMessage jsonData, xhr: xhr
      else
       @onCallError 'Cannot connect to server'

     _respond: (data, options, callback) ->
      @errorCallback 'AJAX cannot respond', data
      callback?()

     _send: (data) ->
      data = JSON.stringify data
      xhr = new XMLHttpRequest
      xhr.open 'POST', "http://#{@host}:#{@port}#{@path}"
      xhr.onreadystatechange = =>
       @_onRequest xhr
      xhr.setRequestHeader 'Accept', 'application/json'
      #xhr.setRequestHeader 'Content-Type', 'application/json'
      xhr.send data

     _handleResponse: (data, options) ->
      for f in @wrappers.handleResponse
       return unless f.apply this, arguments
      if not @callsCache[data.id]?
       @errorCallback "Response without call: #{data.id}", data
       return
      call = @callsCache[data.id]
      if not call.handle data.data, data
       params =
        type: 'poll'
        id: call.id
       params[k] = v for k, v of call.options
       @_send params

##NodeHttpPort class

    class NodeHttpPort extends Port
     constructor: (options, http) ->
      super()
      @host = options.host ? 'localhost'
      @port = options.port ? 80
      @path = options.path ? '/'
      @http = http
      @_createHttpOptions()

     isStreaming: false

     _createHttpOptions: ->
      @httpOptions =
       hostname: @host
       port: @port
       path: @path
       method: 'POST'
       agent: false
       headers:
        accept: 'application/json'
       'content-type': 'application/json'

     _onRequest: (res) ->
      data = ''
      #LOG 'STATUS: ' + res.statusCode
      #LOG 'HEADERS: ' + JSON.stringify res.headers
      res.setEncoding 'utf8'
      res.on 'data', (chunk) ->
       data += chunk
      #LOG 'result', res
      res.on 'end', =>
       try
        jsonData = JSON.parse data
       catch e
        @onCallError 'ParseError', e
        return

       @_handleMessage jsonData, response: res

     _respond: (data, options, callback) ->
      data = JSON.stringify data
      res = options.response
      res.setHeader 'content-length', data.length
      if callback?
       res.once 'finish', ->
        callback()
       res.once 'close', ->
        callback()

      res.write data
      res.end()

     _send: (data, callbacks) ->
      data = JSON.stringify data
      options = @httpOptions
      options.headers['content-length'] = data.length

      req = @http.request options, @_onRequest.bind this
      delete options.headers['content-length']
      req.on 'error', (e) ->
       callbacks.fail? e

      req.write data
      req.end()

     _handleResponse: (data, options) ->
      for f in @wrappers.handleResponse
       return unless f.apply this, arguments
      if not @callsCache[data.id]?
       @errorCallback "Response without call: #{data.id}", data
       return
      call = @callsCache[data.id]
      if not call.handle data.data, data
       params =
        type: 'poll'
        id: call.id
       params[k] = v for k, v of call.options
       @_send params, call.callbacks

##NodeHttpServerPort class

    class NodeHttpServerPort extends Port
     constructor: (options, http) ->
      super()
      @port = options.port
      @http = http

     isStreaming: false

     _onRequest: (req, res) ->
      data = ''
      res.setHeader 'content-type', 'application/json'

      req.on 'data', (chunk) ->
       data += chunk
      req.on 'end', =>
       try
        jsonData = JSON.parse data
       catch e
        @errorCallback 'ParseError', e
        return

       @_handleMessage jsonData, response: res

     _respond: (data, options, callback) ->
      data = JSON.stringify data
      res = options.response
      res.setHeader 'content-length', data.length
      if callback?
       res.once 'finish', ->
        callback()
       res.once 'close', ->
        callback()

      res.write data
      res.end()

     listen: ->
      @server = @http.createServer @_onRequest.bind this
      @server.listen @port

     _handlePoll: (data, options) ->
      for f in @wrappers.handleCall
       return unless f.apply this, arguments
      if not @responses[data.id]?
       @onHandleError "Poll without response: #{data.id}", data, options
       return
      @responses[data.id].setOptions options

#IO Module

    IO =
     addPort: (name, port) ->
      IO[name] = port

     ports:
      WorkerPort: WorkerPort
      SocketPort: SocketPort
      AjaxHttpPort: AjaxHttpPort
      NodeHttpPort: NodeHttpPort
      NodeHttpServerPort: NodeHttpServerPort
      ServerSocketPort: ServerSocketPort

     Helpers:
      progress: (from, to, func) ->
       (progress, data) ->
        func? progress * (to - from) + from, data

     setup_____: (options) ->
      options.workers ?= []
      switch options.platform
       when 'ui'
        for worker in options.workers
         IO.addPort worker.name, new WorkerPort worker.js
        for socket in options.sockets
         IO.addPort socket.name, new SocketPort socket.url
       when 'node'
        for socket in options.listenSockets
         IO.addPort socket.name, new SocketListenPort socket.port
        for socket in options.sockets
         IO.addPort socket.name, new SocketPort socket.url
       when 'worker'
        IO.addPort 'UI', new WorkerListenPort _self

    if exports?
     module.exports = IO
    else
     _self.IO = IO