<commit_before>/**
 * hive.js
 * Copyright (C) 2013-2015 Marcel Klehr <mklehr@gmx.net>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
"use strict";
var koa = require('koa')
  , mount = require('koa-mount')
  , router = require('koa-router')
  , jsonBody = require('koa-json-body')
  , parseUpload = require('co-busboy')
  , WritableBuffer = require('stream-buffers').WritableStreamBuffer
  , JSONAPI = require('waterline-to-jsonapi')
  , qs = require('qs')

module.exports = setup
module.exports.consumes = ['http', 'auth', 'hooks', 'orm', 'sync', 'ot', 'importexport']

function setup(plugin, imports, register) {
  var httpApp = imports.http
    , auth = imports.auth
    , hooks = imports.hooks
    , orm = imports.orm
    , sync = imports.sync
    , ot = imports.ot
    , importexport = imports.importexport

  var api = koa()
  httpApp.use(mount('/api', api))

  var APIv1 = koa()
  api.use(mount('/v1', APIv1))

  APIv1.use(function *(next) {
    try {
      if(!this.accepts('application/vnd.api+json')) {
	return this.throw(406)
      }
      yield next;
    } catch (err) {
      this.app.emit('error', err, this)
      this.status = err.status || 500
      this.body = JSONAPI().errors(err)
    }
  })

  APIv1.use(function* (next) {
    var credentials = this.request.query.access_token
      , type = "token"
    if(!credentials) {
      credentials = this.get('Authorization')
      if(!credentials) return this.throw(401)
      var credParts = credentials.split(' ')
      type = credParts[0]
      credentials = credParts[1]
    }
    try {
      this.user = yield auth.authenticate(type, credentials)
    }catch(e) {console.log(e.stack ||e)}

    if(!this.user) {
      return this.throw(401)
    }
    yield next
  })

  APIv1.use(router(APIv1))

  function toWaterline(jsonAPIBody) {
    if(Array.isArray(jsonAPIBody.data)) return jsonAPIBody.data.map(toWaterline)
    var newItem = jsonAPIBody.data.attributes || {id: jsonAPIBody.data.id}
    for(var rel in jsonAPIBody.data.relationships) {
      var linkage = jsonAPIBody.data.relationships[rel].data
      if(Array.isArray(linkage)) {
	newItem[rel] = linkage.map(resource => resource.id)
      }else if(linkage) {
	newItem[rel] = linkage.id
      }
    }
    return newItem
  }

  hooks.on('orm:initialized', function*(models) {
    var VERBS = ['post', 'get', 'patch', 'delete', 'put']
      , jsonapi = JSONAPI(orm)

    Object.keys(orm.collections).forEach((model) => {

      // Routes for CRUD

      APIv1.post('/'+model+'s', jsonBody(), function*(next) {
	if(!(yield auth.authorize(this.user, model+':create', {body: this.request.body}))) {
	  this.throw(403)
	}
        if(!this.is('application/vnd.api+json')) {
          this.throw(415) // Unsupported media type
        }

	// Build a waterline-conformant object
	var newItem = toWaterline(this.request.body)
	  , data
	// create item
	if(orm.collections[model].override_create) {
	  data = yield orm.collections[model].override_create(newItem)
	}else {
	  data = yield orm.collections[model].create(newItem)
	}

        this.status = 201
	this.body = jsonapi.single(data, model, {fields: qs.parse(this.querystring).fields})
      })

      APIv1.get('/'+model+'s/:id', function*(next) {
        if(!(yield auth.authorize(this.user, model+':read', {id: this.params.id}))) {
          this.throw(403)
        }

	var data = yield orm.collections[model].findOne({id: this.params.id})
	if(!data) this.throw(404)
        this.body = jsonapi.single(data, model, {fields: qs.parse(this.querystring).fields})
      })

      APIv1.patch('/'+model+'s/:id', jsonBody(), function*(next) {
	if(!(yield auth.authorize(this.user, model+':write', {body: this.request.body, id: this.params.id}))) {
	  this.throw(403)
	}
        if(!this.is('application/vnd.api+json')) {
          this.throw(415) // Unsupported media type
        }
	if(this.request.body.data.id !== this.params.id) {
	  this.throw(409)
	}
        var oldModel
	if(!(oldModel = yield orm.collections[model].findOne({id: this.params.id}))) {
	  this.throw(404)
	}
	yield orm.collections[model].update({id: this.params.id}, Object.assign({}, oldModel, toWaterline(this.request.body)))
	var data = yield orm.collections[model].findOne({id: this.params.id})
        this.body = jsonapi.single(data, model, {fields: qs.parse(this.querystring).fields})
      })

      APIv1.delete('/'+model+'s/:id', function * (next) {
	if(!(yield auth.authorize(this.user, model+':destroy', {id: this.params.id}))) {
	  this.throw(403)
	}

	var item = yield orm.collections[model].findOne({id: this.params.id})
	if(!item) this.throw(404)
	yield item.destroy()

	this.status = 204 // No Content
        this.body = null
      })

      // Routes for relationships

      Object.keys(orm.collections[model]._attributes)
      .filter((attr) => {
        // only relations
        return models[model].attributes[attr].model
        || models[model].attributes[attr].collection
      })
      .forEach((relation) => {
        var isToMany = !!models[model].attributes[relation].collection
          , relatedModel = models[model].attributes[relation].model
                         || models[model].attributes[relation].collection

        APIv1.get('/'+model+'s/:id/relationships/'+relation, function*(next) {
          // XXX: Not sure how to infer a sensible authorize token
          if(!(yield auth.authorize(this.user, model+':read', {id: this.params.id}))) {
	    this.throw(403)
	  }

          if(!this.is('application/vnd.api+json')) {
	    this.throw(415) // Unsupported media type
	  }

          var item = yield orm.collections[model].findOne({id: this.params.id}).populate(relation)
          if(!item) this.throw(404)
          this.body = jsonapi.relation(item, model, relation, {fields: qs.parse(this.querystring).fields})
        })

        if(!isToMany) {
          APIv1.patch('/'+model+'s/:id/relationships/'+relation, jsonBody(), function*(next) {
            if(!(yield auth.authorize(this.user, model+':read', {id: this.params.id}))) {
	      this.throw(403)
	    }

            if(!this.is('application/vnd.api+json')) {
              this.throw(415) // Unsupported media type
            }

            var item = yield orm.collections[model].findOne({id: this.params.id})

            // Not found?
            if(!item) {
              this.throw(404)
            }

            var data = toWaterline(this.request.body)

            // This is a to-one relation... No arrays!
            if(Array.isArray(data)) {
              this.throw(400)
            }

            item[relation] = null !== data? data.id : null
            yield item.save()

            // all is well? Say nothing.
            this.status = 204
            this.body = null
          })
        }else{
          APIv1.patch('/'+model+'s/:id/relationships/'+relation, function*(next) {
            this.throw(403)
          })

          APIv1.post('/'+model+'s/:id/relationships/'+relation, jsonBody(), function*(next) {
            if(!(yield auth.authorize(this.user, model+':read', {id: this.params.id}))) {
	      this.throw(403)
            }
            if(!this.is('application/vnd.api+json')) {
              this.throw(415) // Unsupported media type
            }
            var item = yield orm.collections[model].findOne({id: this.params.id})

            // Not found?
            if(!item) {
              this.throw(404)
            }

            var data = toWaterline(this.request.body)

            // Must be an array
            if(!Array.isArray(data)) {
              this.throw(400)
            }

            // check all first
            for(var i=0; i < data.length; i++) {
              var newRel = data[i]
              if(!(yield orm.collections[relatedModel].findOne({id: newRel.id}))) {
                this.throw(400)
              }
            }
            // ... then add them all
            for(var i=0; i < data.length; i++) {
              var newRel = data[i]
              item[relation].add(newRel.id)
            }
            yield item.save()

            // All is well? Say nothing.
            this.status = 204
            this.body = null
          })

          APIv1.delete('/'+model+'s/:id/relationships/'+relation, jsonBody(), function*(next) {
            if(!(yield auth.authorize(this.user, model+':read', {id: this.params.id}))) {
	      this.throw(403)
	    }
            var item = yield orm.collections[model].findOne({id: this.params.id})
            var data = toWaterline(this.request.body)

            // Must be an array
            if(!Array.isArray(data)) {
              this.throw(400)
            }

            for(var i=0; i < data.length; i++) {
              var newRel = data[i]
              item[relation].remove(newRel.id)
            }
            yield item.save()

            // All is well? Say nothing.
            this.status = 204
            this.body = null
          })
        }
      })

      // Routes for class methods

      Object.keys(orm.collections[model]).forEach((method) => {
        // Automatically create routes for VERB_action class methods -> VERB /models/:id/action
        if(VERBS.some((verb) => method.indexOf(verb+'_') === 0)) {
          var verb = method.split('_')[0]
            , action = method.substr(verb.length+1)
          APIv1[verb]('/'+model+'s/:id/'+action, jsonBody(), models[model][method])
        }else
        // Automatically create routes for VERB class methods -> VERB /models/:id?
        if(VERBS.some((verb) => method.indexOf(verb) === 0)) {
          var verb = method.split('_')[0]
            , action = method.substr(verb.length+1)
          APIv1[verb]('/'+model+'s/:id?', jsonBody(), models[model][method])
        }
      })
    })

    var Document = models.document
      , Snapshot = models.snapshot
      , User = models.user

    APIv1
      .post('/documents/:document/snapshots', jsonBody(), function * (next) {
          if(!(yield auth.authorize(this.user, 'document:change', this.params))) {
            return this.throw(403)
          }
          if(!this.accepts('json')) {
           return this.throw(406)
          }
          var doc = yield Document.findOne({id: this.params.document})
          if(!doc) this.throw(404)

          doc = yield sync.getDocument(doc.id)
          var edit = yield function(cb) {
            doc.receiveEdit(JSON.stringify({
              cs: this.request.body.changes
            , parent: this.request.body.parent
            , user: this.user
            }), null, cb)
          }

          this.body = yield Snapshot.findOne({id: edit.id})
        })

      .post('/documents/:document/import', function * (next) {
          if(!(yield auth.authorize(this.user, 'document:change', this.params))) {
            return this.throw(403)
          }
          var doc = yield Document.findOne({id: this.params.document})
          if(!doc) this.throw(404)

          try {
            var parts = parseUpload(this, { autoFields: true})
              , part, bufferStream
            while(part = yield parts) {
              bufferStream = new WritableBuffer()
              var interval = setInterval(() => {
                if(bufferStream.size() > 8*1024*1024) {
                  part.unpipe()
                  this.throw('Attachment is bigger than 8MB', 400)
                  clearInterval(interval)
                }
              }, 100)
              yield function (cb) {
                part.on('end', cb)
                part.pipe(bufferStream)
              }
              clearInterval(interval)
              yield importexport.import(doc.id, this.user
              , part.mime, bufferStream.getContents())
            }
          }catch(e) {
            console.error(e.stack ||e)
            return this.throw(e.message, 500)
          }

          this.body = {message: 'ok'}
        })

      .get('/documents/:document/snapshots', function * (next) {
          if(!(yield auth.authorize(this.user, 'document:read', this.params))) {
            return this.throw(403)
          }

          // returns all snapshots since X
          if(this.query.since) {
            var sinceSnapshot = yield Snapshot.findOne({id: this.query.since})
            if(!sinceSnapshot) this.throw(404)
            if(sinceSnapshot.document != this.params.document) this.throw(400)

            var snapshots = []
             , s = sinceSnapshot
            while(s = yield Snapshot.findOne({parent: s.id}) ) {
              snapshots.push(s)
            }
            this.body = snapshots
            return
          }

          var doc = yield Document.findOne({id: this.params.document}).populate('snapshots')
          this.body = doc.snapshots // XXX: Allow streamin'
        })

      .get('/snapshots/:snapshot/export', function * () {
        if(!(yield auth.authorize(this.user, 'snapshot:show', this.params))) {
          return this.throw(403)
        }
        var snapshot = yield Snapshot.findOne({id:this.params.snapshot})
        if(!snapshot) this.throw(404)
        var document = yield Document.findOne({id:snapshot.document})
        var type = this.query.type || document.type
        this.type = type
        this.body = yield importexport.export(snapshot.id, type)
      })
  })

  register()
}
<commit_msg>Fix: no mime check for GET<commit_after>/**
 * hive.js
 * Copyright (C) 2013-2015 Marcel Klehr <mklehr@gmx.net>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
"use strict";
var koa = require('koa')
  , mount = require('koa-mount')
  , router = require('koa-router')
  , jsonBody = require('koa-json-body')
  , parseUpload = require('co-busboy')
  , WritableBuffer = require('stream-buffers').WritableStreamBuffer
  , JSONAPI = require('waterline-to-jsonapi')
  , qs = require('qs')

module.exports = setup
module.exports.consumes = ['http', 'auth', 'hooks', 'orm', 'sync', 'ot', 'importexport']

function setup(plugin, imports, register) {
  var httpApp = imports.http
    , auth = imports.auth
    , hooks = imports.hooks
    , orm = imports.orm
    , sync = imports.sync
    , ot = imports.ot
    , importexport = imports.importexport

  var api = koa()
  httpApp.use(mount('/api', api))

  var APIv1 = koa()
  api.use(mount('/v1', APIv1))

  APIv1.use(function *(next) {
    try {
      if(!this.accepts('application/vnd.api+json')) {
	return this.throw(406)
      }
      yield next;
    } catch (err) {
      this.app.emit('error', err, this)
      this.status = err.status || 500
      this.body = JSONAPI().errors(err)
    }
  })

  APIv1.use(function* (next) {
    var credentials = this.request.query.access_token
      , type = "token"
    if(!credentials) {
      credentials = this.get('Authorization')
      if(!credentials) return this.throw(401)
      var credParts = credentials.split(' ')
      type = credParts[0]
      credentials = credParts[1]
    }
    try {
      this.user = yield auth.authenticate(type, credentials)
    }catch(e) {console.log(e.stack ||e)}

    if(!this.user) {
      return this.throw(401)
    }
    yield next
  })

  APIv1.use(router(APIv1))

  function toWaterline(jsonAPIBody) {
    if(Array.isArray(jsonAPIBody.data)) return jsonAPIBody.data.map(toWaterline)
    var newItem = jsonAPIBody.data.attributes || {id: jsonAPIBody.data.id}
    for(var rel in jsonAPIBody.data.relationships) {
      var linkage = jsonAPIBody.data.relationships[rel].data
      if(Array.isArray(linkage)) {
	newItem[rel] = linkage.map(resource => resource.id)
      }else if(linkage) {
	newItem[rel] = linkage.id
      }
    }
    return newItem
  }

  hooks.on('orm:initialized', function*(models) {
    var VERBS = ['post', 'get', 'patch', 'delete', 'put']
      , jsonapi = JSONAPI(orm)

    Object.keys(orm.collections).forEach((model) => {

      // Routes for CRUD

      APIv1.post('/'+model+'s', jsonBody(), function*(next) {
	if(!(yield auth.authorize(this.user, model+':create', {body: this.request.body}))) {
	  this.throw(403)
	}
        if(!this.is('application/vnd.api+json')) {
          this.throw(415) // Unsupported media type
        }

	// Build a waterline-conformant object
	var newItem = toWaterline(this.request.body)
	  , data
	// create item
	if(orm.collections[model].override_create) {
	  data = yield orm.collections[model].override_create(newItem)
	}else {
	  data = yield orm.collections[model].create(newItem)
	}

        this.status = 201
	this.body = jsonapi.single(data, model, {fields: qs.parse(this.querystring).fields})
      })

      APIv1.get('/'+model+'s/:id', function*(next) {
        if(!(yield auth.authorize(this.user, model+':read', {id: this.params.id}))) {
          this.throw(403)
        }

	var data = yield orm.collections[model].findOne({id: this.params.id})
	if(!data) this.throw(404)
        this.body = jsonapi.single(data, model, {fields: qs.parse(this.querystring).fields})
      })

      APIv1.patch('/'+model+'s/:id', jsonBody(), function*(next) {
	if(!(yield auth.authorize(this.user, model+':write', {body: this.request.body, id: this.params.id}))) {
	  this.throw(403)
	}
        if(!this.is('application/vnd.api+json')) {
          this.throw(415) // Unsupported media type
        }
	if(this.request.body.data.id !== this.params.id) {
	  this.throw(409)
	}
        var oldModel
	if(!(oldModel = yield orm.collections[model].findOne({id: this.params.id}))) {
	  this.throw(404)
	}
	yield orm.collections[model].update({id: this.params.id}, Object.assign({}, oldModel, toWaterline(this.request.body)))
	var data = yield orm.collections[model].findOne({id: this.params.id})
        this.body = jsonapi.single(data, model, {fields: qs.parse(this.querystring).fields})
      })

      APIv1.delete('/'+model+'s/:id', function * (next) {
	if(!(yield auth.authorize(this.user, model+':destroy', {id: this.params.id}))) {
	  this.throw(403)
	}

	var item = yield orm.collections[model].findOne({id: this.params.id})
	if(!item) this.throw(404)
	yield item.destroy()

	this.status = 204 // No Content
        this.body = null
      })

      // Routes for relationships

      Object.keys(orm.collections[model]._attributes)
      .filter((attr) => {
        // only relations
        return models[model].attributes[attr].model
        || models[model].attributes[attr].collection
      })
      .forEach((relation) => {
        var isToMany = !!models[model].attributes[relation].collection
          , relatedModel = models[model].attributes[relation].model
                         || models[model].attributes[relation].collection

        APIv1.get('/'+model+'s/:id/relationships/'+relation, function*(next) {
          // XXX: Not sure how to infer a sensible authorize token
          if(!(yield auth.authorize(this.user, model+':read', {id: this.params.id}))) {
	    this.throw(403)
          }

          var item = yield orm.collections[model].findOne({id: this.params.id}).populate(relation)
          if(!item) this.throw(404)
          this.body = jsonapi.relation(item, model, relation, {fields: qs.parse(this.querystring).fields})
        })

        if(!isToMany) {
          APIv1.patch('/'+model+'s/:id/relationships/'+relation, jsonBody(), function*(next) {
            if(!(yield auth.authorize(this.user, model+':read', {id: this.params.id}))) {
	      this.throw(403)
	    }

            if(!this.is('application/vnd.api+json')) {
              this.throw(415) // Unsupported media type
            }

            var item = yield orm.collections[model].findOne({id: this.params.id})

            // Not found?
            if(!item) {
              this.throw(404)
            }

            var data = toWaterline(this.request.body)

            // This is a to-one relation... No arrays!
            if(Array.isArray(data)) {
              this.throw(400)
            }

            item[relation] = null !== data? data.id : null
            yield item.save()

            // all is well? Say nothing.
            this.status = 204
            this.body = null
          })
        }else{
          APIv1.patch('/'+model+'s/:id/relationships/'+relation, function*(next) {
            this.throw(403)
          })

          APIv1.post('/'+model+'s/:id/relationships/'+relation, jsonBody(), function*(next) {
            if(!(yield auth.authorize(this.user, model+':read', {id: this.params.id}))) {
	      this.throw(403)
            }
            if(!this.is('application/vnd.api+json')) {
              this.throw(415) // Unsupported media type
            }
            var item = yield orm.collections[model].findOne({id: this.params.id})

            // Not found?
            if(!item) {
              this.throw(404)
            }

            var data = toWaterline(this.request.body)

            // Must be an array
            if(!Array.isArray(data)) {
              this.throw(400)
            }

            // check all first
            for(var i=0; i < data.length; i++) {
              var newRel = data[i]
              if(!(yield orm.collections[relatedModel].findOne({id: newRel.id}))) {
                this.throw(400)
              }
            }
            // ... then add them all
            for(var i=0; i < data.length; i++) {
              var newRel = data[i]
              item[relation].add(newRel.id)
            }
            yield item.save()

            // All is well? Say nothing.
            this.status = 204
            this.body = null
          })

          APIv1.delete('/'+model+'s/:id/relationships/'+relation, jsonBody(), function*(next) {
            if(!(yield auth.authorize(this.user, model+':read', {id: this.params.id}))) {
	      this.throw(403)
	    }
            var item = yield orm.collections[model].findOne({id: this.params.id})
            var data = toWaterline(this.request.body)

            // Must be an array
            if(!Array.isArray(data)) {
              this.throw(400)
            }

            for(var i=0; i < data.length; i++) {
              var newRel = data[i]
              item[relation].remove(newRel.id)
            }
            yield item.save()

            // All is well? Say nothing.
            this.status = 204
            this.body = null
          })
        }
      })

      // Routes for class methods

      Object.keys(orm.collections[model]).forEach((method) => {
        // Automatically create routes for VERB_action class methods -> VERB /models/:id/action
        if(VERBS.some((verb) => method.indexOf(verb+'_') === 0)) {
          var verb = method.split('_')[0]
            , action = method.substr(verb.length+1)
          APIv1[verb]('/'+model+'s/:id/'+action, jsonBody(), models[model][method])
        }else
        // Automatically create routes for VERB class methods -> VERB /models/:id?
        if(VERBS.some((verb) => method.indexOf(verb) === 0)) {
          var verb = method.split('_')[0]
            , action = method.substr(verb.length+1)
          APIv1[verb]('/'+model+'s/:id?', jsonBody(), models[model][method])
        }
      })
    })

    var Document = models.document
      , Snapshot = models.snapshot
      , User = models.user

    APIv1
      .post('/documents/:document/snapshots', jsonBody(), function * (next) {
          if(!(yield auth.authorize(this.user, 'document:change', this.params))) {
            return this.throw(403)
          }
          if(!this.accepts('json')) {
           return this.throw(406)
          }
          var doc = yield Document.findOne({id: this.params.document})
          if(!doc) this.throw(404)

          doc = yield sync.getDocument(doc.id)
          var edit = yield function(cb) {
            doc.receiveEdit(JSON.stringify({
              cs: this.request.body.changes
            , parent: this.request.body.parent
            , user: this.user
            }), null, cb)
          }

          this.body = yield Snapshot.findOne({id: edit.id})
        })

      .post('/documents/:document/import', function * (next) {
          if(!(yield auth.authorize(this.user, 'document:change', this.params))) {
            return this.throw(403)
          }
          var doc = yield Document.findOne({id: this.params.document})
          if(!doc) this.throw(404)

          try {
            var parts = parseUpload(this, { autoFields: true})
              , part, bufferStream
            while(part = yield parts) {
              bufferStream = new WritableBuffer()
              var interval = setInterval(() => {
                if(bufferStream.size() > 8*1024*1024) {
                  part.unpipe()
                  this.throw('Attachment is bigger than 8MB', 400)
                  clearInterval(interval)
                }
              }, 100)
              yield function (cb) {
                part.on('end', cb)
                part.pipe(bufferStream)
              }
              clearInterval(interval)
              yield importexport.import(doc.id, this.user
              , part.mime, bufferStream.getContents())
            }
          }catch(e) {
            console.error(e.stack ||e)
            return this.throw(e.message, 500)
          }

          this.body = {message: 'ok'}
        })

      .get('/documents/:document/snapshots', function * (next) {
          if(!(yield auth.authorize(this.user, 'document:read', this.params))) {
            return this.throw(403)
          }

          // returns all snapshots since X
          if(this.query.since) {
            var sinceSnapshot = yield Snapshot.findOne({id: this.query.since})
            if(!sinceSnapshot) this.throw(404)
            if(sinceSnapshot.document != this.params.document) this.throw(400)

            var snapshots = []
             , s = sinceSnapshot
            while(s = yield Snapshot.findOne({parent: s.id}) ) {
              snapshots.push(s)
            }
            this.body = snapshots
            return
          }

          var doc = yield Document.findOne({id: this.params.document}).populate('snapshots')
          this.body = doc.snapshots // XXX: Allow streamin'
        })

      .get('/snapshots/:snapshot/export', function * () {
        if(!(yield auth.authorize(this.user, 'snapshot:show', this.params))) {
          return this.throw(403)
        }
        var snapshot = yield Snapshot.findOne({id:this.params.snapshot})
        if(!snapshot) this.throw(404)
        var document = yield Document.findOne({id:snapshot.document})
        var type = this.query.type || document.type
        this.type = type
        this.body = yield importexport.export(snapshot.id, type)
      })
  })

  register()
}
<|endoftext|>


<commit_before>#! /usr/bin/env node
'use strict';

const CameraController = require('./app/cameraController');
const Logger = require('./app/logger');
const GpioController = require('./app/gpioController');

let logger = new Logger();

// let c = new CameraController();
// logger.log("1. initializing camera controller");
// c.init();
// logger.log("2. camera status: " + c.getStatus());
// logger.log("3. take picture");
// c.takePhoto();
let gpioController = new GpioController();
gpioController.init();

gpioController.turnLEDOn();

gpioController.turnLEDOff();
<commit_msg>minor tweak<commit_after>#! /usr/bin/env node
'use strict';

const CameraController = require('./app/cameraController');
const Logger = require('./app/logger');
const GpioController = require('./app/gpioController');

let logger = new Logger();

// let c = new CameraController();
// logger.log("1. initializing camera controller");
// c.init();
// logger.log("2. camera status: " + c.getStatus());
// logger.log("3. take picture");
// c.takePhoto();
let gpioController = new GpioController();
gpioController.init();

gpioController.turnLEDOn();

setTimeout(gpioController.turnLEDOff(), 3000);

<|endoftext|>


<commit_before>'use strict';
const path = require('path');
const os = require('os');
const util = require('util');
const childProcess = require('child_process');
const crossSpawn = require('cross-spawn');
const stripFinalNewline = require('strip-final-newline');
const npmRunPath = require('npm-run-path');
const isStream = require('is-stream');
const _getStream = require('get-stream');
const mergeStream = require('merge-stream');
const pFinally = require('p-finally');
const onExit = require('signal-exit');
const stdio = require('./lib/stdio');

const TEN_MEGABYTES = 1000 * 1000 * 10;

const SPACES_REGEXP = / +/g;

function handleArgs(file, args, options = {}) {
	const parsed = crossSpawn._parse(file, args, options);
	file = parsed.command;
	args = parsed.args;
	options = parsed.options;

	options = {
		maxBuffer: TEN_MEGABYTES,
		buffer: true,
		stripFinalNewline: true,
		preferLocal: true,
		localDir: options.cwd || process.cwd(),
		encoding: 'utf8',
		reject: true,
		cleanup: true,
		...options,
		windowsHide: true
	};

	if (options.extendEnv !== false) {
		options.env = {
			...process.env,
			...options.env
		};
	}

	if (options.preferLocal) {
		options.env = npmRunPath.env({
			...options,
			cwd: options.localDir
		});
	}

	options.stdio = stdio(options);

	if (process.platform === 'win32' && path.basename(file, '.exe') === 'cmd') {
		// #116
		args.unshift('/q');
	}

	return {file, args, options, parsed};
}

function handleInput(spawned, input) {
	// Checking for stdin is workaround for https://github.com/nodejs/node/issues/26852
	// TODO: Remove `|| spawned.stdin === undefined` once we drop support for Node.js <=12.2.0
	if (input === undefined || spawned.stdin === undefined) {
		return;
	}

	if (isStream(input)) {
		input.pipe(spawned.stdin);
	} else {
		spawned.stdin.end(input);
	}
}

function handleOutput(options, value, error) {
	if (typeof value !== 'string' && !Buffer.isBuffer(value)) {
		// When `execa.sync()` errors, we normalize it to '' to mimic `execa()`
		return error === undefined ? undefined : '';
	}

	if (options.stripFinalNewline) {
		return stripFinalNewline(value);
	}

	return value;
}

function makeAllStream(spawned) {
	if (!spawned.stdout && !spawned.stderr) {
		return;
	}

	const mixed = mergeStream();

	if (spawned.stdout) {
		mixed.add(spawned.stdout);
	}

	if (spawned.stderr) {
		mixed.add(spawned.stderr);
	}

	return mixed;
}

function getStream(process, stream, {encoding, buffer, maxBuffer}) {
	if (!process[stream]) {
		return;
	}

	let ret;

	if (!buffer) {
		// TODO: Use `ret = util.promisify(stream.finished)(process[stream]);` when targeting Node.js 10
		ret = new Promise((resolve, reject) => {
			process[stream]
				.once('end', resolve)
				.once('error', reject);
		});
	} else if (encoding) {
		ret = _getStream(process[stream], {
			encoding,
			maxBuffer
		});
	} else {
		ret = _getStream.buffer(process[stream], {maxBuffer});
	}

	return ret.catch(error => {
		error.stream = stream;
		error.message = `${stream} ${error.message}`;
		throw error;
	});
}

function makeError(result, options) {
	const {stdout, stderr, signal} = result;
	let {error} = result;
	const {code, joinedCommand, timedOut, isCanceled, killed, parsed: {options: {timeout}}} = options;

	const [exitCodeName, exitCode] = getCode(result, code);

	const prefix = getErrorPrefix({timedOut, timeout, signal, exitCodeName, exitCode, isCanceled});
	const message = `Command ${prefix}: ${joinedCommand}`;

	if (error instanceof Error) {
		error.message = `${message}\n${error.message}`;
	} else {
		error = new Error(message);
	}

	error.command = joinedCommand;
	delete error.code;
	error.exitCode = exitCode;
	error.exitCodeName = exitCodeName;
	error.stdout = stdout;
	error.stderr = stderr;

	if ('all' in result) {
		error.all = result.all;
	}

	error.failed = true;
	error.timedOut = timedOut;
	error.isCanceled = isCanceled;
	error.killed = killed && !timedOut;
	// `signal` emitted on `spawned.on('exit')` event can be `null`. We normalize
	// it to `undefined`
	error.signal = signal || undefined;

	return error;
}

function getCode({error = {}}, code) {
	if (error.code) {
		return [error.code, os.constants.errno[error.code]];
	}

	if (Number.isInteger(code)) {
		return [util.getSystemErrorName(-code), code];
	}

	return [];
}

function getErrorPrefix({timedOut, timeout, signal, exitCodeName, exitCode, isCanceled}) {
	if (timedOut) {
		return `timed out after ${timeout} milliseconds`;
	}

	if (isCanceled) {
		return 'was canceled';
	}

	if (signal) {
		return `was killed with ${signal}`;
	}

	if (exitCode !== undefined) {
		return `failed with exit code ${exitCode} (${exitCodeName})`;
	}

	return 'failed';
}

function joinCommand(file, args = []) {
	if (!Array.isArray(args)) {
		return file;
	}

	return [file, ...args].join(' ');
}

function shouldForceKill(signal, options, killResult) {
	return ((typeof signal === 'string' &&
		signal.toUpperCase() === 'SIGTERM') ||
		signal === os.constants.signals.SIGTERM) &&
		options.forceKill !== false &&
		killResult;
}

const execa = (file, args, options) => {
	const parsed = handleArgs(file, args, options);
	const {encoding, buffer, maxBuffer} = parsed.options;
	const joinedCommand = joinCommand(file, args);

	let spawned;
	try {
		spawned = childProcess.spawn(parsed.file, parsed.args, parsed.options);
	} catch (error) {
		return Promise.reject(makeError({error, stdout: '', stderr: '', all: ''}, {
			joinedCommand,
			parsed,
			timedOut: false,
			isCanceled: false,
			killed: false
		}));
	}

	const originalKill = spawned.kill.bind(spawned);
	spawned.kill = (signal = 'SIGTERM', options = {}) => {
		const killResult = originalKill(signal);
		if (shouldForceKill(signal, options, killResult)) {
			const forceKillAfter = Number.isInteger(options.forceKillAfter) ?
				options.forceKillAfter :
				5000;
			setTimeout(() => {
				originalKill('SIGKILL');
			}, forceKillAfter).unref();
		}

		return killResult;
	};

	// #115
	let removeExitHandler;
	if (parsed.options.cleanup && !parsed.options.detached) {
		removeExitHandler = onExit(() => {
			spawned.kill();
		});
	}

	let timeoutId;
	let timedOut = false;
	let isCanceled = false;

	const cleanup = () => {
		if (timeoutId !== undefined) {
			clearTimeout(timeoutId);
			timeoutId = undefined;
		}

		if (removeExitHandler) {
			removeExitHandler();
		}
	};

	if (parsed.options.timeout > 0) {
		timeoutId = setTimeout(() => {
			timeoutId = undefined;
			timedOut = true;
			spawned.kill(parsed.options.killSignal);
		}, parsed.options.timeout);
	}

	// TODO: Use native "finally" syntax when targeting Node.js 10
	const processDone = pFinally(new Promise((resolve, reject) => {
		spawned.on('exit', (code, signal) => {
			if (timedOut) {
				return reject(Object.assign(new Error('Timed out'), {code, signal}));
			}

			resolve({code, signal});
		});

		spawned.on('error', error => {
			reject(error);
		});

		if (spawned.stdin) {
			spawned.stdin.on('error', error => {
				reject(error);
			});
		}
	}), cleanup);

	function destroy() {
		if (spawned.stdout) {
			spawned.stdout.destroy();
		}

		if (spawned.stderr) {
			spawned.stderr.destroy();
		}

		if (spawned.all) {
			spawned.all.destroy();
		}
	}

	const handlePromise = () => {
		const processComplete = Promise.all([
			processDone,
			getStream(spawned, 'stdout', {encoding, buffer, maxBuffer}),
			getStream(spawned, 'stderr', {encoding, buffer, maxBuffer}),
			getStream(spawned, 'all', {encoding, buffer, maxBuffer: maxBuffer * 2})
		]);

		const finalize = async () => {
			let results;
			try {
				results = await processComplete;
			} catch (error) {
				const {stream, code, signal} = error;
				results = [{error, stream, code, signal}, '', '', ''];
			}

			const [result, stdout, stderr, all] = results;
			result.stdout = handleOutput(parsed.options, stdout);
			result.stderr = handleOutput(parsed.options, stderr);
			result.all = handleOutput(parsed.options, all);

			if (result.error || result.code !== 0 || result.signal !== null) {
				const error = makeError(result, {
					code: result.code,
					joinedCommand,
					parsed,
					timedOut,
					isCanceled,
					killed: spawned.killed
				});

				if (!parsed.options.reject) {
					return error;
				}

				throw error;
			}

			return {
				command: joinedCommand,
				exitCode: 0,
				exitCodeName: 'SUCCESS',
				stdout: result.stdout,
				stderr: result.stderr,
				all: result.all,
				failed: false,
				timedOut: false,
				isCanceled: false,
				killed: false
			};
		};

		// TODO: Use native "finally" syntax when targeting Node.js 10
		return pFinally(finalize(), destroy);
	};

	crossSpawn._enoent.hookChildProcess(spawned, parsed.parsed);

	handleInput(spawned, parsed.options.input);

	spawned.all = makeAllStream(spawned);

	// eslint-disable-next-line promise/prefer-await-to-then
	spawned.then = (onFulfilled, onRejected) => handlePromise().then(onFulfilled, onRejected);
	spawned.catch = onRejected => handlePromise().catch(onRejected);
	spawned.cancel = () => {
		if (spawned.kill()) {
			isCanceled = true;
		}
	};

	// TODO: Remove the `if`-guard when targeting Node.js 10
	if (Promise.prototype.finally) {
		spawned.finally = onFinally => handlePromise().finally(onFinally);
	}

	return spawned;
};

module.exports = execa;

module.exports.sync = (file, args, options) => {
	const parsed = handleArgs(file, args, options);
	const joinedCommand = joinCommand(file, args);

	if (isStream(parsed.options.input)) {
		throw new TypeError('The `input` option cannot be a stream in sync mode');
	}

	const result = childProcess.spawnSync(parsed.file, parsed.args, parsed.options);
	result.stdout = handleOutput(parsed.options, result.stdout, result.error);
	result.stderr = handleOutput(parsed.options, result.stderr, result.error);

	if (result.error || result.status !== 0 || result.signal !== null) {
		const error = makeError(result, {
			code: result.status,
			joinedCommand,
			parsed,
			timedOut: result.error && result.error.errno === 'ETIMEDOUT',
			isCanceled: false,
			killed: result.signal !== null
		});

		if (!parsed.options.reject) {
			return error;
		}

		throw error;
	}

	return {
		command: joinedCommand,
		exitCode: 0,
		exitCodeName: 'SUCCESS',
		stdout: result.stdout,
		stderr: result.stderr,
		failed: false,
		timedOut: false,
		isCanceled: false,
		killed: false
	};
};

// Allow spaces to be escaped by a backslash if not meant as a delimiter
function handleEscaping(tokens, token, index) {
	if (index === 0) {
		return [token];
	}

	const previousToken = tokens[tokens.length - 1];

	if (previousToken.endsWith('\\')) {
		return [...tokens.slice(0, -1), `${previousToken.slice(0, -1)} ${token}`];
	}

	return [...tokens, token];
}

function parseCommand(command) {
	return command
		.trim()
		.split(SPACES_REGEXP)
		.reduce(handleEscaping, []);
}

module.exports.command = (command, options) => {
	const [file, ...args] = parseCommand(command);
	return execa(file, args, options);
};

module.exports.commandSync = (command, options) => {
	const [file, ...args] = parseCommand(command);
	return execa.sync(file, args, options);
};
<commit_msg>Refactor `kill()` (#272)<commit_after>'use strict';
const path = require('path');
const os = require('os');
const util = require('util');
const childProcess = require('child_process');
const crossSpawn = require('cross-spawn');
const stripFinalNewline = require('strip-final-newline');
const npmRunPath = require('npm-run-path');
const isStream = require('is-stream');
const _getStream = require('get-stream');
const mergeStream = require('merge-stream');
const pFinally = require('p-finally');
const onExit = require('signal-exit');
const stdio = require('./lib/stdio');

const TEN_MEGABYTES = 1000 * 1000 * 10;

const SPACES_REGEXP = / +/g;

function handleArgs(file, args, options = {}) {
	const parsed = crossSpawn._parse(file, args, options);
	file = parsed.command;
	args = parsed.args;
	options = parsed.options;

	options = {
		maxBuffer: TEN_MEGABYTES,
		buffer: true,
		stripFinalNewline: true,
		preferLocal: true,
		localDir: options.cwd || process.cwd(),
		encoding: 'utf8',
		reject: true,
		cleanup: true,
		...options,
		windowsHide: true
	};

	if (options.extendEnv !== false) {
		options.env = {
			...process.env,
			...options.env
		};
	}

	if (options.preferLocal) {
		options.env = npmRunPath.env({
			...options,
			cwd: options.localDir
		});
	}

	options.stdio = stdio(options);

	if (process.platform === 'win32' && path.basename(file, '.exe') === 'cmd') {
		// #116
		args.unshift('/q');
	}

	return {file, args, options, parsed};
}

function handleInput(spawned, input) {
	// Checking for stdin is workaround for https://github.com/nodejs/node/issues/26852
	// TODO: Remove `|| spawned.stdin === undefined` once we drop support for Node.js <=12.2.0
	if (input === undefined || spawned.stdin === undefined) {
		return;
	}

	if (isStream(input)) {
		input.pipe(spawned.stdin);
	} else {
		spawned.stdin.end(input);
	}
}

function handleOutput(options, value, error) {
	if (typeof value !== 'string' && !Buffer.isBuffer(value)) {
		// When `execa.sync()` errors, we normalize it to '' to mimic `execa()`
		return error === undefined ? undefined : '';
	}

	if (options.stripFinalNewline) {
		return stripFinalNewline(value);
	}

	return value;
}

function makeAllStream(spawned) {
	if (!spawned.stdout && !spawned.stderr) {
		return;
	}

	const mixed = mergeStream();

	if (spawned.stdout) {
		mixed.add(spawned.stdout);
	}

	if (spawned.stderr) {
		mixed.add(spawned.stderr);
	}

	return mixed;
}

function getStream(process, stream, {encoding, buffer, maxBuffer}) {
	if (!process[stream]) {
		return;
	}

	let ret;

	if (!buffer) {
		// TODO: Use `ret = util.promisify(stream.finished)(process[stream]);` when targeting Node.js 10
		ret = new Promise((resolve, reject) => {
			process[stream]
				.once('end', resolve)
				.once('error', reject);
		});
	} else if (encoding) {
		ret = _getStream(process[stream], {
			encoding,
			maxBuffer
		});
	} else {
		ret = _getStream.buffer(process[stream], {maxBuffer});
	}

	return ret.catch(error => {
		error.stream = stream;
		error.message = `${stream} ${error.message}`;
		throw error;
	});
}

function makeError(result, options) {
	const {stdout, stderr, signal} = result;
	let {error} = result;
	const {code, joinedCommand, timedOut, isCanceled, killed, parsed: {options: {timeout}}} = options;

	const [exitCodeName, exitCode] = getCode(result, code);

	const prefix = getErrorPrefix({timedOut, timeout, signal, exitCodeName, exitCode, isCanceled});
	const message = `Command ${prefix}: ${joinedCommand}`;

	if (error instanceof Error) {
		error.message = `${message}\n${error.message}`;
	} else {
		error = new Error(message);
	}

	error.command = joinedCommand;
	delete error.code;
	error.exitCode = exitCode;
	error.exitCodeName = exitCodeName;
	error.stdout = stdout;
	error.stderr = stderr;

	if ('all' in result) {
		error.all = result.all;
	}

	error.failed = true;
	error.timedOut = timedOut;
	error.isCanceled = isCanceled;
	error.killed = killed && !timedOut;
	// `signal` emitted on `spawned.on('exit')` event can be `null`. We normalize
	// it to `undefined`
	error.signal = signal || undefined;

	return error;
}

function getCode({error = {}}, code) {
	if (error.code) {
		return [error.code, os.constants.errno[error.code]];
	}

	if (Number.isInteger(code)) {
		return [util.getSystemErrorName(-code), code];
	}

	return [];
}

function getErrorPrefix({timedOut, timeout, signal, exitCodeName, exitCode, isCanceled}) {
	if (timedOut) {
		return `timed out after ${timeout} milliseconds`;
	}

	if (isCanceled) {
		return 'was canceled';
	}

	if (signal) {
		return `was killed with ${signal}`;
	}

	if (exitCode !== undefined) {
		return `failed with exit code ${exitCode} (${exitCodeName})`;
	}

	return 'failed';
}

function joinCommand(file, args = []) {
	if (!Array.isArray(args)) {
		return file;
	}

	return [file, ...args].join(' ');
}

function spawnedKill(kill, signal = 'SIGTERM', options = {}) {
	const killResult = kill(signal);
	setKillTimeout(kill, signal, options, killResult);
	return killResult;
}

function setKillTimeout(kill, signal, options, killResult) {
	if (!shouldForceKill(signal, options, killResult)) {
		return;
	}

	const forceKillAfter = Number.isInteger(options.forceKillAfter) ?
		options.forceKillAfter :
		5000;
	setTimeout(() => kill('SIGKILL'), forceKillAfter).unref();
}

function shouldForceKill(signal, options, killResult) {
	return ((typeof signal === 'string' &&
		signal.toUpperCase() === 'SIGTERM') ||
		signal === os.constants.signals.SIGTERM) &&
		options.forceKill !== false &&
		killResult;
}

const execa = (file, args, options) => {
	const parsed = handleArgs(file, args, options);
	const {encoding, buffer, maxBuffer} = parsed.options;
	const joinedCommand = joinCommand(file, args);

	let spawned;
	try {
		spawned = childProcess.spawn(parsed.file, parsed.args, parsed.options);
	} catch (error) {
		return Promise.reject(makeError({error, stdout: '', stderr: '', all: ''}, {
			joinedCommand,
			parsed,
			timedOut: false,
			isCanceled: false,
			killed: false
		}));
	}

	const kill = spawned.kill.bind(spawned);
	spawned.kill = spawnedKill.bind(null, kill);

	// #115
	let removeExitHandler;
	if (parsed.options.cleanup && !parsed.options.detached) {
		removeExitHandler = onExit(() => {
			spawned.kill();
		});
	}

	let timeoutId;
	let timedOut = false;
	let isCanceled = false;

	const cleanup = () => {
		if (timeoutId !== undefined) {
			clearTimeout(timeoutId);
			timeoutId = undefined;
		}

		if (removeExitHandler) {
			removeExitHandler();
		}
	};

	if (parsed.options.timeout > 0) {
		timeoutId = setTimeout(() => {
			timeoutId = undefined;
			timedOut = true;
			spawned.kill(parsed.options.killSignal);
		}, parsed.options.timeout);
	}

	// TODO: Use native "finally" syntax when targeting Node.js 10
	const processDone = pFinally(new Promise((resolve, reject) => {
		spawned.on('exit', (code, signal) => {
			if (timedOut) {
				return reject(Object.assign(new Error('Timed out'), {code, signal}));
			}

			resolve({code, signal});
		});

		spawned.on('error', error => {
			reject(error);
		});

		if (spawned.stdin) {
			spawned.stdin.on('error', error => {
				reject(error);
			});
		}
	}), cleanup);

	function destroy() {
		if (spawned.stdout) {
			spawned.stdout.destroy();
		}

		if (spawned.stderr) {
			spawned.stderr.destroy();
		}

		if (spawned.all) {
			spawned.all.destroy();
		}
	}

	const handlePromise = () => {
		const processComplete = Promise.all([
			processDone,
			getStream(spawned, 'stdout', {encoding, buffer, maxBuffer}),
			getStream(spawned, 'stderr', {encoding, buffer, maxBuffer}),
			getStream(spawned, 'all', {encoding, buffer, maxBuffer: maxBuffer * 2})
		]);

		const finalize = async () => {
			let results;
			try {
				results = await processComplete;
			} catch (error) {
				const {stream, code, signal} = error;
				results = [{error, stream, code, signal}, '', '', ''];
			}

			const [result, stdout, stderr, all] = results;
			result.stdout = handleOutput(parsed.options, stdout);
			result.stderr = handleOutput(parsed.options, stderr);
			result.all = handleOutput(parsed.options, all);

			if (result.error || result.code !== 0 || result.signal !== null) {
				const error = makeError(result, {
					code: result.code,
					joinedCommand,
					parsed,
					timedOut,
					isCanceled,
					killed: spawned.killed
				});

				if (!parsed.options.reject) {
					return error;
				}

				throw error;
			}

			return {
				command: joinedCommand,
				exitCode: 0,
				exitCodeName: 'SUCCESS',
				stdout: result.stdout,
				stderr: result.stderr,
				all: result.all,
				failed: false,
				timedOut: false,
				isCanceled: false,
				killed: false
			};
		};

		// TODO: Use native "finally" syntax when targeting Node.js 10
		return pFinally(finalize(), destroy);
	};

	crossSpawn._enoent.hookChildProcess(spawned, parsed.parsed);

	handleInput(spawned, parsed.options.input);

	spawned.all = makeAllStream(spawned);

	// eslint-disable-next-line promise/prefer-await-to-then
	spawned.then = (onFulfilled, onRejected) => handlePromise().then(onFulfilled, onRejected);
	spawned.catch = onRejected => handlePromise().catch(onRejected);
	spawned.cancel = () => {
		if (spawned.kill()) {
			isCanceled = true;
		}
	};

	// TODO: Remove the `if`-guard when targeting Node.js 10
	if (Promise.prototype.finally) {
		spawned.finally = onFinally => handlePromise().finally(onFinally);
	}

	return spawned;
};

module.exports = execa;

module.exports.sync = (file, args, options) => {
	const parsed = handleArgs(file, args, options);
	const joinedCommand = joinCommand(file, args);

	if (isStream(parsed.options.input)) {
		throw new TypeError('The `input` option cannot be a stream in sync mode');
	}

	const result = childProcess.spawnSync(parsed.file, parsed.args, parsed.options);
	result.stdout = handleOutput(parsed.options, result.stdout, result.error);
	result.stderr = handleOutput(parsed.options, result.stderr, result.error);

	if (result.error || result.status !== 0 || result.signal !== null) {
		const error = makeError(result, {
			code: result.status,
			joinedCommand,
			parsed,
			timedOut: result.error && result.error.errno === 'ETIMEDOUT',
			isCanceled: false,
			killed: result.signal !== null
		});

		if (!parsed.options.reject) {
			return error;
		}

		throw error;
	}

	return {
		command: joinedCommand,
		exitCode: 0,
		exitCodeName: 'SUCCESS',
		stdout: result.stdout,
		stderr: result.stderr,
		failed: false,
		timedOut: false,
		isCanceled: false,
		killed: false
	};
};

// Allow spaces to be escaped by a backslash if not meant as a delimiter
function handleEscaping(tokens, token, index) {
	if (index === 0) {
		return [token];
	}

	const previousToken = tokens[tokens.length - 1];

	if (previousToken.endsWith('\\')) {
		return [...tokens.slice(0, -1), `${previousToken.slice(0, -1)} ${token}`];
	}

	return [...tokens, token];
}

function parseCommand(command) {
	return command
		.trim()
		.split(SPACES_REGEXP)
		.reduce(handleEscaping, []);
}

module.exports.command = (command, options) => {
	const [file, ...args] = parseCommand(command);
	return execa(file, args, options);
};

module.exports.commandSync = (command, options) => {
	const [file, ...args] = parseCommand(command);
	return execa.sync(file, args, options);
};
<|endoftext|>


<commit_before>'use strict';
var TendrilPromise = require('bluebird/js/main/promise')();
var _ = require('lodash');
var fs = require('fs');
var events = require('events');
var readdir = TendrilPromise.promisify(fs.readdir);

module.exports = Tendril;

function Tendril() {

  // services which are not lazily loaded are loaded at the first resolve
  this.requested = [];
  this.constructors = {};
  this.services = { tendril: TendrilPromise.resolve(this).bind(this) };
  this.eventEmitter = new events.EventEmitter();
  this._isTendril = true;
}

/*
 * @typedef {Object} IncludeConfig
 *
 * @property {Boolean} [inject=true] - should attempt to inject function
 * @property {Boolean} [lazy=true] - only load if required by a sub-service
 */

/*
 * @param {String|Object} name - if object, keys are names and values services
 * @param {Anything|Function} constructor - the service, or resolved
 * @param {IncludeConfig} config
 */
Tendril.prototype.include = function include(name, constructor, config) {
  var self = this

  // Legacy support for include(name, constructor, inject, lazy)
  if (typeof config === 'boolean') {
    config = {
      inject: config
    };
  } else {
    config = _.defaults(config || {}, {
      inject: true,
      lazy: true
    });
  }

  if (arguments.length === 4) {
    config.lazy = arguments[3] == null ? true : arguments[3];
  }
  // end legacy support

  return TendrilPromise.try(function () {
    config = _.defaults(config || {}, {
      inject: true,
      lazy: true
    })
    var inject = config.inject;
    var lazy = config.lazy;

    // already initialized service
    if (!inject) {
      self.services[name] = TendrilPromise.resolve(constructor);
    }

    if (!lazy) {
      self.requested.push(name);
    }

    // Including an object, where values are services and keys are names
    if (typeof name === 'object') {
      return TendrilPromise.all(_.map(name, function (constructor, serviceName) {
        return self.include(serviceName, constructor, config);
      }));

      // constructor is a function or has a setup function on it
    } else if (inject && (typeof constructor === 'function' ||
        typeof constructor === 'object' &&
        typeof constructor.setup === 'function')) {

      if (typeof constructor === 'object') {
        constructor = constructor.setup;
      }

      self.constructors[name] = getConstructor(constructor);
    } else if (inject && Array.isArray(constructor)) {
      self.constructors[name] = getConstructor(constructor);
    } else {
      self.services[name] = TendrilPromise.resolve(constructor);
    }
  })
  .then(function () {
    return self;
  }).bind(this);
};

/*
 * returns a service
 *
 * @param {String} name
 */
Tendril.prototype._getService = function getService(name) {
  var self = this;
  return TendrilPromise.try(function () {
    if (_.isArray(name)) {
      return TendrilPromise.all(_.map(name, getService));
    }

    if (self.services[name]) {
      return self.services[name];
    }

    if (!self.constructors[name]) {
      return TendrilPromise.reject(missingDependencyError(name, self.constructors));
    }

    var circle = circularDependencies(name, name, self.constructors);
    if (circle) {
      return TendrilPromise.reject(new Error('Circular Dependency: ' +
                                       [name].concat(circle).join(' --> ')));
    }

    var constructor = self.constructors[name];

    self.services[name] = TendrilPromise.map(
                            constructor.params,
                            self._getService.bind(self))
    .spread(constructor.fn)
    .tap(function () {
      self.eventEmitter.emit('serviceLoad', {
        name: name,
        instance: self.services[name]
      });
    });

    return self.services[name];
  }).bind(this);
};

/*
 * @param {Function|Array<paramNames..,fn>} - param names are service names
 */
Tendril.prototype.resolve = function resolve(fn, error) {
  var self = this;
  return TendrilPromise.try(function () {
    // default values
    fn = fn || _.noop;
    error = error || function (err) {
      setImmediate(function () {
        throw err;
      });
    };

    var constructor = getConstructor(fn);

    // resolve non-lazy services
    return TendrilPromise.each(self.requested, self._getService.bind(self))
    .then(function () {

      // resolve requested services, and pass them into the function
      return TendrilPromise.map(constructor.params, self._getService.bind(self))
        .spread(constructor.fn);
    }).then(null, error)
  })
  .then(function () {
    return self;
  }).bind(this);
};

/*
 * @param name - event name (e.g. serviceLoad)
 * @param fn - callback fn -> { name: 'serviceName', instance: {Service} }
 */
Tendril.prototype.on = function on(name, fn) {
  var self = this;
  return TendrilPromise.try(function () {
    self.eventEmitter.on(name, fn);
    return self;
  }).bind(this);
};

/*
 * @typedef {Object} Crawl
 *
 * @property {String} path - absolute path of directory to crawl
 * @property {String} [postfix=''] - String to append to filenames as services
 * @property {Boolean} [lazy=true] - only load if required by another service
 */

/*
 * crawl a directory
 *
 * @param {Crawl|Array<Crawl>} crawl
 */
Tendril.prototype.crawl = function _crawl(crawl) {
  var self = this;
  return TendrilPromise.try(function () {

    if (Array.isArray(crawl)) {
      return TendrilPromise.all(_.map(crawl, self.crawl.bind(self)));
    }

    return readdir(crawl.path).map(function (file) {
      var serviceName = file.replace(/\.js$/, '') + (crawl.postfix || '');
      return includeFile(serviceName, file, crawl.path, crawl.lazy);
    });
  })
  .then(function () {
    return self;
  }).bind(this);

  function includeFile(serviceName, file, path, lazy) {
    var isDir = /^([^.]|\.\.)+$/.test(file);
    var isJsFile = /\.js$/.test(file);

    var requirePath = path + '/' + file;
    var service;

    // verify index.js exists
    if (isDir) {
      return readdir(requirePath).then(function (files) {
        if (_.contains(files, 'index.js')) {
          service = require(requirePath);
          return self.include(serviceName, service, {
            lazy: lazy
          });
        }
      });
    } else if (isJsFile) {
      service = require(requirePath);
      return self.include(serviceName, service, {
        lazy: lazy
      });
    }
  }
};

TendrilPromise.prototype = _.assign(TendrilPromise.prototype,
                      _.transform(Object.keys(Tendril.prototype),
                      function (methods, methodName) {

  methods[methodName] = function () {
    var tendril = this._boundTo;
    var args = arguments;
    return this._then(function () {
      if (!tendril || !tendril._isTendril) {
        throw new Error('Missing tendril object binding')
      }
      return tendril[methodName].apply(tendril, args);
    });
  }
}, {}))

/*
 * @param {String} name
 * @param {Object<String, Constructor>} constructors
 *
 * @returns {Error}
 */
function missingDependencyError(name, constructors) {
  var message = 'Missing Dependency: ' + name;
  var dependencies = _.mapValues(constructors, function (constructor) {
    return constructor.params;
  });
  var missing = _.reduce(dependencies, function (missing, dep, service) {
    if (_.contains(dep, name)) {
      return missing.concat(service);
    }
    return missing;
  }, []);
  if (missing.length) {
    message += '\nDepended on by: ' + missing.join(', ');
  }

  return new Error(message);
}

/*
 * typedef {Object} Constructor
 *
 * @property {Function} fn
 * @property {Array<String>} params
 */

/*
 * @param {String} rootName
 * @param {String} childName
 * @param {Object<String, Constructor>} constructors
 *
 * @returns {Array<String>|null} - Service names, null if no circular deps
 */
function circularDependencies(rootName, childName, constructors) {
  var constructor = constructors[childName];
  if (!rootName || !constructor) {
    return null;
  }

  var containsSelf = _.contains(constructor.params, rootName);

  if (containsSelf) {
    return [rootName];
  }

  var innerCircularDependencies = _.reduce(constructor.params,
    function (circle, serviceName) {
    var deeper = circularDependencies(rootName, serviceName, constructors);

    if (deeper) {
      return circle.concat([serviceName]).concat(deeper);
    }

    return circle;

  }, []);

  if (innerCircularDependencies.length) {
    return innerCircularDependencies;
  }

  return null;
}

/*
 * @param {Function|Array<Function>} fn
 *
 * @returns {Array<String>} - parameter names
 */
function getParams(fn) {
  if (!fn) {
    return [];
  }

  var functionExp = /^function\s*[^\(]*\(\s*([^\)]*)\)/m;
  var commentsExp = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;
  var argExp = /^\s*(\S+?)\s*$/;

  var fnString = fn.toString().replace(commentsExp, '');
  var match = fnString.match(functionExp);
  var params = match && match[1];

  if (!match || !params) {
    return [];
  }

  return _.map(params.split(','), function (param) {
    return param.match(argExp)[1];
  });
}

/*
 * @param {Function|Array<...Function>} fn
 *
 * @returns {Constructor}
 */
function getConstructor(fn) {
  if (Array.isArray(fn)) {
    var func = fn.pop();
    var params = fn;
    return {
      fn: func,
      params: params
    };
  } else {
    return {
      fn: fn,
      params: getParams(fn)
    };
  }
}
<commit_msg>jshint<commit_after>'use strict';
var TendrilPromise = require('bluebird/js/main/promise')();
var _ = require('lodash');
var fs = require('fs');
var events = require('events');
var readdir = TendrilPromise.promisify(fs.readdir);

module.exports = Tendril;

function Tendril() {

  // services which are not lazily loaded are loaded at the first resolve
  this.requested = [];
  this.constructors = {};
  this.services = { tendril: TendrilPromise.resolve(this).bind(this) };
  this.eventEmitter = new events.EventEmitter();
  this._isTendril = true;
}

/*
 * @typedef {Object} IncludeConfig
 *
 * @property {Boolean} [inject=true] - should attempt to inject function
 * @property {Boolean} [lazy=true] - only load if required by a sub-service
 */

/*
 * @param {String|Object} name - if object, keys are names and values services
 * @param {Anything|Function} constructor - the service, or resolved
 * @param {IncludeConfig} config
 */
Tendril.prototype.include = function include(name, constructor, config) {
  var self = this;

  // Legacy support for include(name, constructor, inject, lazy)
  if (typeof config === 'boolean') {
    config = {
      inject: config
    };
  } else {
    config = _.defaults(config || {}, {
      inject: true,
      lazy: true
    });
  }

  if (arguments.length === 4) {
    config.lazy = arguments[3] == null ? true : arguments[3];
  }
  // end legacy support

  return TendrilPromise.try(function () {
    config = _.defaults(config || {}, {
      inject: true,
      lazy: true
    });
    var inject = config.inject;
    var lazy = config.lazy;

    // already initialized service
    if (!inject) {
      self.services[name] = TendrilPromise.resolve(constructor);
    }

    if (!lazy) {
      self.requested.push(name);
    }

    // Including an object, where values are services and keys are names
    if (typeof name === 'object') {
      return TendrilPromise.all(_.map(name, function (constructor, serviceName) {
        return self.include(serviceName, constructor, config);
      }));

      // constructor is a function or has a setup function on it
    } else if (inject && (typeof constructor === 'function' ||
        typeof constructor === 'object' &&
        typeof constructor.setup === 'function')) {

      if (typeof constructor === 'object') {
        constructor = constructor.setup;
      }

      self.constructors[name] = getConstructor(constructor);
    } else if (inject && Array.isArray(constructor)) {
      self.constructors[name] = getConstructor(constructor);
    } else {
      self.services[name] = TendrilPromise.resolve(constructor);
    }
  })
  .then(function () {
    return self;
  }).bind(this);
};

/*
 * returns a service
 *
 * @param {String} name
 */
Tendril.prototype._getService = function getService(name) {
  var self = this;
  return TendrilPromise.try(function () {
    if (_.isArray(name)) {
      return TendrilPromise.all(_.map(name, getService));
    }

    if (self.services[name]) {
      return self.services[name];
    }

    if (!self.constructors[name]) {
      return TendrilPromise.reject(missingDependencyError(name, self.constructors));
    }

    var circle = circularDependencies(name, name, self.constructors);
    if (circle) {
      return TendrilPromise.reject(new Error('Circular Dependency: ' +
                                       [name].concat(circle).join(' --> ')));
    }

    var constructor = self.constructors[name];

    self.services[name] = TendrilPromise.map(
                            constructor.params,
                            self._getService.bind(self))
    .spread(constructor.fn)
    .tap(function () {
      self.eventEmitter.emit('serviceLoad', {
        name: name,
        instance: self.services[name]
      });
    });

    return self.services[name];
  }).bind(this);
};

/*
 * @param {Function|Array<paramNames..,fn>} - param names are service names
 */
Tendril.prototype.resolve = function resolve(fn, error) {
  var self = this;
  return TendrilPromise.try(function () {
    // default values
    fn = fn || _.noop;
    error = error || function (err) {
      setImmediate(function () {
        throw err;
      });
    };

    var constructor = getConstructor(fn);

    // resolve non-lazy services
    return TendrilPromise.each(self.requested, self._getService.bind(self))
    .then(function () {

      // resolve requested services, and pass them into the function
      return TendrilPromise.map(constructor.params, self._getService.bind(self))
        .spread(constructor.fn);
    }).then(null, error);
  })
  .then(function () {
    return self;
  }).bind(this);
};

/*
 * @param name - event name (e.g. serviceLoad)
 * @param fn - callback fn -> { name: 'serviceName', instance: {Service} }
 */
Tendril.prototype.on = function on(name, fn) {
  var self = this;
  return TendrilPromise.try(function () {
    self.eventEmitter.on(name, fn);
    return self;
  }).bind(this);
};

/*
 * @typedef {Object} Crawl
 *
 * @property {String} path - absolute path of directory to crawl
 * @property {String} [postfix=''] - String to append to filenames as services
 * @property {Boolean} [lazy=true] - only load if required by another service
 */

/*
 * crawl a directory
 *
 * @param {Crawl|Array<Crawl>} crawl
 */
Tendril.prototype.crawl = function _crawl(crawl) {
  var self = this;
  return TendrilPromise.try(function () {

    if (Array.isArray(crawl)) {
      return TendrilPromise.all(_.map(crawl, self.crawl.bind(self)));
    }

    return readdir(crawl.path).map(function (file) {
      var serviceName = file.replace(/\.js$/, '') + (crawl.postfix || '');
      return includeFile(serviceName, file, crawl.path, crawl.lazy);
    });
  })
  .then(function () {
    return self;
  }).bind(this);

  function includeFile(serviceName, file, path, lazy) {
    var isDir = /^([^.]|\.\.)+$/.test(file);
    var isJsFile = /\.js$/.test(file);

    var requirePath = path + '/' + file;
    var service;

    // verify index.js exists
    if (isDir) {
      return readdir(requirePath).then(function (files) {
        if (_.contains(files, 'index.js')) {
          service = require(requirePath);
          return self.include(serviceName, service, {
            lazy: lazy
          });
        }
      });
    } else if (isJsFile) {
      service = require(requirePath);
      return self.include(serviceName, service, {
        lazy: lazy
      });
    }
  }
};

TendrilPromise.prototype = _.assign(TendrilPromise.prototype,
                      _.transform(Object.keys(Tendril.prototype),
                      function (methods, methodName) {

  methods[methodName] = function () {
    var tendril = this._boundTo;
    var args = arguments;
    return this._then(function () {
      if (!tendril || !tendril._isTendril) {
        throw new Error('Missing tendril object binding');
      }
      return tendril[methodName].apply(tendril, args);
    });
  };
}, {}));

/*
 * @param {String} name
 * @param {Object<String, Constructor>} constructors
 *
 * @returns {Error}
 */
function missingDependencyError(name, constructors) {
  var message = 'Missing Dependency: ' + name;
  var dependencies = _.mapValues(constructors, function (constructor) {
    return constructor.params;
  });
  var missing = _.reduce(dependencies, function (missing, dep, service) {
    if (_.contains(dep, name)) {
      return missing.concat(service);
    }
    return missing;
  }, []);
  if (missing.length) {
    message += '\nDepended on by: ' + missing.join(', ');
  }

  return new Error(message);
}

/*
 * typedef {Object} Constructor
 *
 * @property {Function} fn
 * @property {Array<String>} params
 */

/*
 * @param {String} rootName
 * @param {String} childName
 * @param {Object<String, Constructor>} constructors
 *
 * @returns {Array<String>|null} - Service names, null if no circular deps
 */
function circularDependencies(rootName, childName, constructors) {
  var constructor = constructors[childName];
  if (!rootName || !constructor) {
    return null;
  }

  var containsSelf = _.contains(constructor.params, rootName);

  if (containsSelf) {
    return [rootName];
  }

  var innerCircularDependencies = _.reduce(constructor.params,
    function (circle, serviceName) {
    var deeper = circularDependencies(rootName, serviceName, constructors);

    if (deeper) {
      return circle.concat([serviceName]).concat(deeper);
    }

    return circle;

  }, []);

  if (innerCircularDependencies.length) {
    return innerCircularDependencies;
  }

  return null;
}

/*
 * @param {Function|Array<Function>} fn
 *
 * @returns {Array<String>} - parameter names
 */
function getParams(fn) {
  if (!fn) {
    return [];
  }

  var functionExp = /^function\s*[^\(]*\(\s*([^\)]*)\)/m;
  var commentsExp = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;
  var argExp = /^\s*(\S+?)\s*$/;

  var fnString = fn.toString().replace(commentsExp, '');
  var match = fnString.match(functionExp);
  var params = match && match[1];

  if (!match || !params) {
    return [];
  }

  return _.map(params.split(','), function (param) {
    return param.match(argExp)[1];
  });
}

/*
 * @param {Function|Array<...Function>} fn
 *
 * @returns {Constructor}
 */
function getConstructor(fn) {
  if (Array.isArray(fn)) {
    var func = fn.pop();
    var params = fn;
    return {
      fn: func,
      params: params
    };
  } else {
    return {
      fn: fn,
      params: getParams(fn)
    };
  }
}
<|endoftext|>


<commit_before>var fs      = require('fs');
var spawn   = require('child_process').spawn;

              require('colors');
var program = require('commander');
var esprima = require('esprima');
var glob    = require('glob');
var _       = require('lodash');
var request = require('request');

var headers = {

  // GitHub API requires the User-Agent Header to be set
  'User-Agent': 'mdlint'
};

var tokenFile = __dirname + '/authtoken.txt';

module.exports = function () {

  // Use Auth Token if present
  if (fs.existsSync(tokenFile)) {
    headers.Authorization = 'token ' + fs.readFileSync(tokenFile, 'utf8');
  }

  program
    .version('0.0.0')
    .option('-s, --silent',  'only report failing lints');

  program
    .command('repo <repo>')
    .description('lints a README from a GitHub repo')
    .action(function (repo) {
      fetchREADME(repo);
    });

  program
    .command('user <username>')
    .description('lints all READMEs from a user\'s GitHub repos')
    .action(function (user) {
      request({
        uri: 'https://api.github.com/users/' + user + '/repos',
        headers: headers
      }, function (error, response, body) {
        JSON.parse(body)
          .forEach(function (repo) {
            fetchREADME(repo.full_name);
          });
      });
    });

  program
    .command('glob <glob>')
    .description('lints local markdown files that match a file glob')
    .action(function (fileGlob) {
      glob.sync(fileGlob).forEach(function (file) {
        lintMarkdown(fs.readFileSync(file, 'utf8'), file);
      });
    });

  program
    .command('query <query>')
    .option('--page', 'The page of results to return. Defaults to 0.')
    .description('lints READMEs from repos returned by a GitHub query.')
    .action(function (query) {
      request({
        uri: 'https://api.github.com/legacy/repos/search/' +
             query +
             '?language=JavaScript' +
             '&start_page=' + program.page || '0',
        headers: headers
      }, function (error, response, body) {
        if (!error && response.statusCode === 200) {
          JSON.parse(body)
            .repositories
            .forEach(function (repo) {
              fetchREADME(repo.owner + '/' + repo.name);
            });
        } else {
          if (response.headers['x-ratelimit-remaining'] === '0') {
            getAuthToken();
          } else {
            console.log(body);
            console.log(response.headers);
            console.log('Unable to reach the GitHub API :('.red);
          }
          return;
        }
      });
    });

  program
    .command('*')
    .action(function () {
      program.help();
    });

  program
    .parse(process.argv);

  if (!program.args.length) {
    program.help();
  }
};

/**
 * Fetches a README from GitHub
 * @param  {String} repo URL of repo to fetch
 */
function fetchREADME (repo) {
  request({
    uri: 'https://api.github.com/repos/' + repo + '/readme',
    headers: _.extend(headers, {

      // Get raw README content
      'Accept': 'application/vnd.github.VERSION.raw'
    })
  },
  function (error, response, body) {
    if (!error && response.statusCode === 200) {
      lintMarkdown(body, repo);
    } else {
      if (response.headers['x-ratelimit-remaining'] === '0') {
        getAuthToken();
      } else {
        console.log('README for https://github.com/' + repo.blue + ' not found.'.red);
        return;
      }
    }
  });
}

/**
 * [parseMarkdown description]
 * @param  {String} markdownContent Content of markdown file
 * @return {Array}                  Array of objects with a language and code property
 */
function parseMarkdown (markdownContent) {
  var codeBlocks =  markdownContent

  .split('```')

  .filter(function (codeBlock, index) {

    // Delete the text in between code sections
    if (index % 2 === 0 ||

        // Remove non JavaScript and JSON code blocks
        (codeBlock.trim().toLowerCase().indexOf('js')         !== 0 &&
         codeBlock.trim().toLowerCase().indexOf('javascript') !== 0)) {
      return false;
    }
    return true;
  })

  .map(function (codeBlock) {
    return {
      lang: codeBlock.split('\n')[0].trim().toLowerCase(),
      code: codeBlock.slice(codeBlock.indexOf('\n') + 1)
    };
  });

  return codeBlocks;
}

/**
 * Parses the JavaScript code blocks from the markdown file
 * @param  {String} body Body of markdown file
 * @param  {String} file Filename
 */
function lintMarkdown (body, file) {

  if (!program.silent) {
    console.log(file.blue.bold);
  }
  var codeBlocks = parseMarkdown(body);
  if (_.all(_.compact(codeBlocks), validateCodeBlock)) {
    if (!program.silent) {
      console.log('Markdown passed linting.\n'.green);
    }
  } else {
    console.log('Markdown failed linting.\n'.red);
  }
}

/**
 * Validates that code blocks are valid JavaScript
 * @param  {Object} code A block of code from the markdown file containg the lang and code
 */
function validateCodeBlock (codeBlock) {
  var lang = codeBlock.lang;
  var code = codeBlock.code;

  if (lang === 'json') {
    try {
      JSON.parse(code);
    } catch (e) {
      console.log(e);
      console.log(code);
      return false;
    }
    return true;
  } else if (lang === 'js' || lang === 'javascript') {
    code = preprocessCode(code);
    try {
      esprima.parse(code, { tolerant: true });
    } catch (e) {

      // Get indeces from lineNumber and column
      var line   = e.lineNumber - 1;
      var column = e.column - 1;

      // Highlight error in code
      code = code.split('\n');

      code[line] = code[line].slice(0, column).magenta +
                   code[line][column].red +
                   code[line].slice(column + 1).magenta;

      code = code.join('\n');

      console.log(e);
      console.log(code);
      return false;
    }
    return true;
  }
}

/**
 * Retrieves an auth token so that the user can exceed the uauthenticated rate limit
 */
function getAuthToken () {
  console.log('You have hit the rate limit for unauthenticated requests, please log in to raise your rate limit:\n'.red);

  program.prompt('GitHub Username: ', function (user) {

    console.log('\nAfter entering your password, hit return' + ' twice.\n'.green);
    var authProcess = spawn('curl', [
      '-u',
      user,
      '-d',
      '{"scopes":["repo"],"note":"mdlint"}',
      '-s',
      'https://api.github.com/authorizations'
    ], {
      stdio: [process.stdin, 'pipe', process.stderr]
    });

    authProcess.stdout.setEncoding('utf8');
    authProcess.stdout.on('data', function (data) {
      var response = JSON.parse(data);
      if (response.message) {
        console.log(response.message.red + '\n');
        process.exit();
      } else {
        fs.writeFileSync(tokenFile, response.token);
        console.log('Authenticated :). Now try your lint again. \n'.green);
        process.exit();
      }
    });
  });
}

/**
 * Preprocesses the code block and re-formats it to allow for partial code
 * @param  {String} code A block of code from the markdown file
 * @return {String} Processed code transformed from partial code
 */
function preprocessCode (code) {

  // Remove starting comments
  if (code.indexOf('//') === 0) {
    code = code.slice(code.indexOf('\n'));
  }

  // Starts with an object literal
  if (code.indexOf('{') === 0) {
    code = 'var json = ' + code + ';';

  // Starts with an object property
  } else if (code.indexOf(': {') !== -1 && code.indexOf(': {') < code.indexOf('{')) {
    code = 'var json = {' + code + '};';
  }

  // Starts with an anonymous function
  if (code.indexOf('function') === 0) {
    code = 'var func = ' + code + ';';
  }

  // Contains ...
  return code.replace('...', '');
}<commit_msg>Added support for linting with implicit commands<commit_after>var fs      = require('fs');
var spawn   = require('child_process').spawn;

              require('colors');
var program = require('commander');
var esprima = require('esprima');
var glob    = require('glob');
var _       = require('lodash');
var request = require('request');

var headers = {

  // GitHub API requires the User-Agent Header to be set
  'User-Agent': 'mdlint'
};

var tokenFile = __dirname + '/authtoken.txt';

module.exports = function () {

  // Use Auth Token if present
  if (fs.existsSync(tokenFile)) {
    headers.Authorization = 'token ' + fs.readFileSync(tokenFile, 'utf8');
  }

  program
    .version('0.0.0')
    .option('-s, --silent',  'only report failing lints');

  program
    .command('repo <repo>')
    .description('lints a README from a GitHub repo')
    .action(function (repo) {
      fetchREADME(repo);
    });

  program
    .command('user <username>')
    .description('lints all READMEs from a user\'s GitHub repos')
    .action(function (user) {
      getUserREADMEs(user);
    });

  program
    .command('glob <glob>')
    .description('lints local markdown files that match a file glob')
    .action(function (fileGlob) {
      glob.sync(fileGlob).forEach(function (file) {
        lintMarkdown(fs.readFileSync(file, 'utf8'), file);
      });
    });

  program
    .command('query <query>')
    .option('--page', 'The page of results to return. Defaults to 0.')
    .description('lints READMEs from repos returned by a GitHub query.')
    .action(function (query) {
      request({
        uri: 'https://api.github.com/legacy/repos/search/' +
             query +
             '?language=JavaScript' +
             '&start_page=' + program.page || '0',
        headers: headers
      }, function (error, response, body) {
        if (!error && response.statusCode === 200) {
          JSON.parse(body)
            .repositories
            .forEach(function (repo) {
              fetchREADME(repo.owner + '/' + repo.name);
            });
        } else {
          if (response.headers['x-ratelimit-remaining'] === '0') {
            getAuthToken();
          } else {
            console.log(body);
            console.log(response.headers);
            console.log('Unable to reach the GitHub API :('.red);
          }
          return;
        }
      });
    });

  program
    .command('*')
    .action(function (command) {
      if (command.indexOf('/') !== -1) {
        fetchREADME(command);
      } else if (command.indexOf('*') !== -1 || command.indexOf('.') !== -1) {
        glob.sync(command).forEach(function (file) {
          lintMarkdown(fs.readFileSync(file, 'utf8'), file);
        });
      } else {
        getUserREADMEs(command);
      }
    });

  program
    .parse(process.argv);

  if (!program.args.length) {
    program.help();
  }
};

/**
 * Fetches READMEs from a user's GitHub repos
 * @param  {String} GitHub username
 */
function getUserREADMEs (user) {
  request({
    uri: 'https://api.github.com/users/' + user + '/repos',
    headers: headers
  }, function (error, response, body) {
    var responseBody = JSON.parse(body);

    if (responseBody.message) {
      console.log('Error: the following user was not found: '.red + user.blue);
      return;
    }

    JSON.parse(body)
      .forEach(function (repo) {
        fetchREADME(repo.full_name);
      });
  });
}

/**
 * Fetches a README from GitHub
 * @param  {String} repo URL of repo to fetch
 */
function fetchREADME (repo) {
  request({
    uri: 'https://api.github.com/repos/' + repo + '/readme',
    headers: _.extend(headers, {

      // Get raw README content
      'Accept': 'application/vnd.github.VERSION.raw'
    })
  },
  function (error, response, body) {
    if (!error && response.statusCode === 200) {
      lintMarkdown(body, repo);
    } else {
      if (response.headers['x-ratelimit-remaining'] === '0') {
        getAuthToken();
      } else {
        console.log('README for https://github.com/' + repo.blue + ' not found.'.red);
        return;
      }
    }
  });
}

/**
 * [parseMarkdown description]
 * @param  {String} markdownContent Content of markdown file
 * @return {Array}                  Array of objects with a language and code property
 */
function parseMarkdown (markdownContent) {
  var codeBlocks =  markdownContent

  .split('```')

  .filter(function (codeBlock, index) {

    // Delete the text in between code sections
    if (index % 2 === 0 ||

        // Remove non JavaScript and JSON code blocks
        (codeBlock.trim().toLowerCase().indexOf('js')         !== 0 &&
         codeBlock.trim().toLowerCase().indexOf('javascript') !== 0)) {
      return false;
    }
    return true;
  })

  .map(function (codeBlock) {
    return {
      lang: codeBlock.split('\n')[0].trim().toLowerCase(),
      code: codeBlock.slice(codeBlock.indexOf('\n') + 1)
    };
  });

  return codeBlocks;
}

/**
 * Parses the JavaScript code blocks from the markdown file
 * @param  {String} body Body of markdown file
 * @param  {String} file Filename
 */
function lintMarkdown (body, file) {

  if (!program.silent) {
    console.log(file.blue.bold);
  }
  var codeBlocks = parseMarkdown(body);
  if (_.all(_.compact(codeBlocks), validateCodeBlock)) {
    if (!program.silent) {
      console.log('Markdown passed linting.\n'.green);
    }
  } else {
    console.log('Markdown failed linting.\n'.red);
  }
}

/**
 * Validates that code blocks are valid JavaScript
 * @param  {Object} code A block of code from the markdown file containg the lang and code
 */
function validateCodeBlock (codeBlock) {
  var lang = codeBlock.lang;
  var code = codeBlock.code;

  if (lang === 'json') {
    try {
      JSON.parse(code);
    } catch (e) {
      console.log(e);
      console.log(code);
      return false;
    }
    return true;
  } else if (lang === 'js' || lang === 'javascript') {
    code = preprocessCode(code);
    try {
      esprima.parse(code, { tolerant: true });
    } catch (e) {

      // Get indeces from lineNumber and column
      var line   = e.lineNumber - 1;
      var column = e.column - 1;

      // Highlight error in code
      code = code.split('\n');

      code[line] = code[line].slice(0, column).magenta +
                   code[line][column].red +
                   code[line].slice(column + 1).magenta;

      code = code.join('\n');

      console.log(e);
      console.log(code);
      return false;
    }
    return true;
  }
}

/**
 * Retrieves an auth token so that the user can exceed the uauthenticated rate limit
 */
function getAuthToken () {
  console.log('You have hit the rate limit for unauthenticated requests, please log in to raise your rate limit:\n'.red);

  program.prompt('GitHub Username: ', function (user) {

    console.log('\nAfter entering your password, hit return' + ' twice.\n'.green);
    var authProcess = spawn('curl', [
      '-u',
      user,
      '-d',
      '{"scopes":["repo"],"note":"mdlint"}',
      '-s',
      'https://api.github.com/authorizations'
    ], {
      stdio: [process.stdin, 'pipe', process.stderr]
    });

    authProcess.stdout.setEncoding('utf8');
    authProcess.stdout.on('data', function (data) {
      var response = JSON.parse(data);
      if (response.message) {
        console.log(response.message.red + '\n');
        process.exit();
      } else {
        fs.writeFileSync(tokenFile, response.token);
        console.log('Authenticated :). Now try your lint again. \n'.green);
        process.exit();
      }
    });
  });
}

/**
 * Preprocesses the code block and re-formats it to allow for partial code
 * @param  {String} code A block of code from the markdown file
 * @return {String} Processed code transformed from partial code
 */
function preprocessCode (code) {

  // Remove starting comments
  if (code.indexOf('//') === 0) {
    code = code.slice(code.indexOf('\n'));
  }

  // Starts with an object literal
  if (code.indexOf('{') === 0) {
    code = 'var json = ' + code + ';';

  // Starts with an object property
  } else if (code.indexOf(': {') !== -1 && code.indexOf(': {') < code.indexOf('{')) {
    code = 'var json = {' + code + '};';
  }

  // Starts with an anonymous function
  if (code.indexOf('function') === 0) {
    code = 'var func = ' + code + ';';
  }

  // Contains ...
  return code.replace('...', '');
}<|endoftext|>


<commit_before>/**
 * FaceNode
 */

var https = require('https');

module.exports = function () {
	"use strict";
	var APP_KEY = null,
		SECRET_KEY = null,
		REGISTERED_URL = null,
		ACCESS_TOKEN = null,
		/**
		* @param: {String} hash
		* @api: public
		*/
		getFBData = function (hash) {
			return {
				host: 'graph.facebook.com',
				port: 443,
				path: hash,
				method: 'GET'
			};
		},
		/**
		* @param: {String} token
		* @api: private
		*/
		setAccessToken = function (token) {
			ACCESS_TOKEN = token;
		};
	//public
	return {
		/**
		* @param: {String} key
		* @api: public
		*/
		setAppKey : function (key) {
			APP_KEY = key;
			return APP_KEY;
		},
		/**
		* @param: {String} key
		* @api: public
		*/
		setSecretKey : function (key) {
			SECRET_KEY = key;
			return SECRET_KEY;
		},
		/**
		* @param: {String} url
		* @api: public
		*/
		setRegisteredURL : function (url) {
			REGISTERED_URL = url;
			return REGISTERED_URL;
		},
		/**
		* @param: {Function} callbackFn
		* @api: public
		*/
		getAuthURL : function (callbackFn) {
			if (!APP_KEY) {
				return false;
			}
			if (typeof REGISTERED_URL === 'undefined') {
				console.log("É necessário um REGISTERED_URL");
				return false;
			}
			callbackFn('https://www.facebook.com/dialog/oauth?client_id=' + APP_KEY + '&redirect_uri=' + encodeURIComponent(REGISTERED_URL));
		},
		/**
		* @param: {String} code
		* @param: {Function} callbackFn
		* @api: public
		*/
		getAccessToken : function (code, callbackFn) {
			if (!SECRET_KEY) {
				return false;
			}
			if (code === '') {
				return false;
			}
			var
				hash = '/oauth/access_token?client_id=' + APP_KEY +
				'&redirect_uri=' +
				encodeURIComponent(REGISTERED_URL) +
				'&client_secret=' + SECRET_KEY +
				'&code=' + code;
			console.log("Consultando getAccessToken em:" + hash);
			https.get(getFBData(hash), function (res) {
				res.setEncoding('utf8');
				res.on('data', function (token) {
					if (token.indexOf('access_token') === -1) {
						token = JSON.parse(token);
						console.log("Ocorreu um erro na requisição do código: " + token.error.message + "-" + token.error.type);
						callbackFn(false);
						return;
					}
					setAccessToken(token);
					callbackFn(token);
				});
			}).on('error', function (e) {
				console.log('Problemas na request para o FB. URL Inválida? ' + e.message);
			});
		},
		/**
		* @param: {Object} params
		* @api: public
		*/
		getGraph : function (params) {
		// get : function(access_token,callbackFn,id) {
			// 	var hash = '/' + (typeof id == 'number'?id:'me') +'?access_token=' + access_token;
			// 	var doRequest = https.get(getFBData(hash),function(res) {
			// 		res.setEncoding('utf8');
			// 		res.on('data', function (data) {
			// 			callbackFn(JSON.parse(data));
			// 		});
			// 	}).on('error', function(e) {
			// 		console.log('Problemas na request -> ' + e.message);
			// 	});
			// },
			// me : function(access_token,callbackFn) {
			// 	this.get(access_token,callbackFn);
			// }
		}
	};
}();<commit_msg>getGraph method<commit_after>/**
 * FaceNode
 * Ricardo Bin <ricardohbin@gmail.com>
 */

var https = require('https');

module.exports = function () {
	"use strict";
	var APP_KEY = null,
		SECRET_KEY = null,
		REGISTERED_URL = null,
		ACCESS_TOKEN = null,
		/**
		* @param: {String} hash
		* @api: public
		*/
		getFBData = function (hash) {
			return {
				host: 'graph.facebook.com',
				port: 443,
				path: hash,
				method: 'GET'
			};
		},
		/**
		* @param: {String} token
		* @api: private
		*/
		setAccessToken = function (token) {
			ACCESS_TOKEN = token;
		};
	//public
	return {
		/**
		* @param: {String} key
		* @api: public
		*/
		setAppKey : function (key) {
			APP_KEY = key;
			return APP_KEY;
		},
		/**
		* @param: {String} key
		* @api: public
		*/
		setSecretKey : function (key) {
			SECRET_KEY = key;
			return SECRET_KEY;
		},
		/**
		* @param: {String} url
		* @api: public
		*/
		setRegisteredURL : function (url) {
			REGISTERED_URL = url;
			return REGISTERED_URL;
		},
		/**
		* @param: {Function} callbackFn
		* @api: public
		*/
		getAuthURL : function (callbackFn) {
			if (!APP_KEY) {
				return false;
			}
			if (typeof REGISTERED_URL === 'undefined') {
				console.log("É necessário um REGISTERED_URL");
				return false;
			}
			callbackFn('https://www.facebook.com/dialog/oauth?client_id=' + APP_KEY + '&redirect_uri=' + encodeURIComponent(REGISTERED_URL));
		},
		/**
		* @param: {String} code
		* @param: {Function} callbackFn
		* @api: public
		*/
		getAccessToken : function (code, callbackFn) {
			if (!SECRET_KEY) {
				return false;
			}
			if (code === '') {
				return false;
			}
			var
				hash = '/oauth/access_token?client_id=' + APP_KEY +
				'&redirect_uri=' +
				encodeURIComponent(REGISTERED_URL) +
				'&client_secret=' + SECRET_KEY +
				'&code=' + code;
			console.log("Consultando getAccessToken em:" + hash);
			https.get(getFBData(hash), function (res) {
				res.setEncoding('utf8');
				res.on('data', function (token) {
					if (token.indexOf('access_token') === -1) {
						token = JSON.parse(token);
						console.log("Ocorreu um erro na requisição do código: " + token.error.message + "-" + token.error.type);
						callbackFn(false);
						return;
					}
					setAccessToken(token);
					callbackFn(token);
				});
			}).on('error', function (e) {
				console.log('Problemas na request para o FB. URL Inválida? ' + e.message);
			});
		},
		/**
		* @param: {Function} callbackFn
		* @param: {Object} params
		* @api: public
		*/
		getGraph : function (callbackFn, params) {
			params = params || {};
			var
				id = params.id || 'me',
				connectionType = params.connectionType || '',
				queryString = params.queryString || '',
				hash = '/' + id + '/' + connectionType + '?access_token=' + ACCESS_TOKEN + '&' + queryString;
			https.get(getFBData(hash), function (res) {
				res.setEncoding('utf8');
				res.on('data', function (data) {
					callbackFn(JSON.parse(data));
				});
			}).on('error', function (e) {
				console.log('Problemas na request -> ' + e.message);
			});
		}
	};
};<|endoftext|>


<commit_before>var RoutesRouter = require("routes")
var url = require("url")
var methods = require("http-methods")
var sendError = require("send-data/error")
var extend = require("xtend")

module.exports = Router

function Router(opts) {
    opts = opts || {}

    var notFound = opts.notFound || defaultNotFound
    var errorHandler = opts.errorHandler || defaultErrorHandler
    var teardown = opts.teardown || noop
    var useDomains = opts.useDomains
    var domain
    var router = new RoutesRouter()

    if (useDomains) {
        domain = require("domain")
    }

    handleRequest.addRoute = function addRoute(uri, fn) {
        if (typeof fn === "object") {
            fn = methods(fn)
        }

        router.addRoute(uri, fn)
    }
    handleRequest.routes = router.routes
    handleRequest.routeMap = router.routeMap
    handleRequest.match = function match(uri) {
        return router.match(uri)
    }

    return handleRequest

    function handleRequest(req, res) {
        if (useDomains) {
            var d = domain.create()
            d.add(req)
            d.add(res)

            d.on("error", function (err) {
                errorHandler(req, res, err)
                teardown(req, res, err)
            })

            d.run(runRoute)
        } else {
            runRoute()
        }

        function runRoute() {
            var route = router.match(url.parse(req.url).pathname)

            if (!route) {
                return notFound(req, res)
            }

            var opts = extend(route.params, {
                params: route.params,
                splats: route.splats
            })

            route.fn(req, res, opts, handleError)
        }

        function handleError(err) {
            if (err) {
                errorHandler(req, res, err)
            }
        }
    }
}

function defaultErrorHandler(req, res, err) {
    sendError(req, res, {
        body: err,
        statusCode: err.statusCode || 500
    })
}

function defaultNotFound(req, res) {
    res.statusCode = 404
    res.end("404 Not Found")
}

function noop() {}
<commit_msg>have err.statusCode === 404 redirect to notFound handler<commit_after>var RoutesRouter = require("routes")
var url = require("url")
var methods = require("http-methods")
var sendError = require("send-data/error")
var extend = require("xtend")

module.exports = Router

function Router(opts) {
    opts = opts || {}

    var notFound = opts.notFound || defaultNotFound
    var errorHandler = opts.errorHandler || defaultErrorHandler
    var teardown = opts.teardown || noop
    var useDomains = opts.useDomains
    var domain
    var router = new RoutesRouter()

    if (useDomains) {
        domain = require("domain")
    }

    handleRequest.addRoute = function addRoute(uri, fn) {
        if (typeof fn === "object") {
            fn = methods(fn)
        }

        router.addRoute(uri, fn)
    }
    handleRequest.routes = router.routes
    handleRequest.routeMap = router.routeMap
    handleRequest.match = function match(uri) {
        return router.match(uri)
    }
    handleRequest.notFound = notFound

    return handleRequest

    function handleRequest(req, res) {
        if (useDomains) {
            var d = domain.create()
            d.add(req)
            d.add(res)

            d.on("error", function (err) {
                errorHandler.call(handleRequest, req, res, err)
                teardown(req, res, err)
            })

            d.run(runRoute)
        } else {
            runRoute()
        }

        function runRoute() {
            var route = router.match(url.parse(req.url).pathname)

            if (!route) {
                return notFound(req, res)
            }

            var opts = extend(route.params, {
                params: route.params,
                splats: route.splats
            })

            route.fn(req, res, opts, handleError)
        }

        function handleError(err) {
            if (err) {
                errorHandler.call(handleRequest, req, res, err)
            }
        }
    }
}

function defaultErrorHandler(req, res, err) {
    if (err.statusCode === 404) {
        return this.notFound(req, res)
    }

    sendError(req, res, {
        body: err,
        statusCode: err.statusCode || 500
    })
}

function defaultNotFound(req, res) {
    res.statusCode = 404
    res.end("404 Not Found")
}

function noop() {}
<|endoftext|>