<commit_before>'use strict';

const GridFsStorage = require('../index');

const multer = require('multer');
const chai = require('chai');
const expect = chai.expect;
const request = require('supertest');
const express = require('express');
const settings = require('./utils/settings');
const mongo = require('mongodb');
const MongoClient = mongo.MongoClient;
const Grid = require('gridfs-stream');
const {files, cleanDb, version} = require('./utils/testutils');
const mute = require('mute');

const sinon = require('sinon');
const sinonChai = require('sinon-chai');
chai.use(sinonChai);

describe('error handling', function () {
  let storage, app, unmute, connectRef, removeRef;

  before(() => {
    unmute = mute(process.stderr);
    app = express();
  });

  it('should throw an error if the identifier function is invoked with an error callback', function (done) {
    storage = GridFsStorage({
      url: settings.mongoUrl(),
      identifier: (req, file, cb) => {
        cb(new Error('Identifier error'));
      }
    });

    const upload = multer({storage});

    app.post('/identifier', upload.single('photo'), (req, res) => {
      res.send({headers: req.headers, files: req.files, body: req.body});
    });

    storage.on('connection', () => {
      request(app)
        .post('/identifier')
        .attach('photo', files[0])
        .end((err, res) => {
          expect(res.serverError).to.equal(true);
          expect(res.error).to.be.an('error');
          expect(res.error.text).to.match(/Error: Identifier error/);
          done();
        });
    });
  });

  it('should throw an error if the filename function is invoked with an error callback', function (done) {
    storage = GridFsStorage({
      url: settings.mongoUrl(),
      filename: (req, file, cb) => {
        cb(new Error('Filename error'));
      }
    });

    const upload = multer({storage});

    app.post('/filename', upload.single('photo'), (req, res) => {
      res.send({headers: req.headers, files: req.files, body: req.body});
    });

    storage.on('connection', () => {
      request(app)
        .post('/filename')
        .attach('photo', files[0])
        .end((err, res) => {
          expect(res.serverError).to.equal(true);
          expect(res.error).to.be.an('error');
          expect(res.error.text).to.match(/Error: Filename error/);
          done();
        });
    });
  });

  it('should throw an error if the metadata is invoked with an error callback', function (done) {
    storage = GridFsStorage({
      url: settings.mongoUrl(),
      metadata: (req, file, cb) => {
        cb(new Error('Metadata error'));
      }
    });

    const upload = multer({storage});

    app.post('/metadata', upload.single('photo'), (req, res) => {
      res.send({headers: req.headers, files: req.files, body: req.body});
    });

    storage.on('connection', function () {
      request(app)
        .post('/metadata')
        .attach('photo', files[0])
        .end((err, res) => {
          expect(res.serverError).to.equal(true);
          expect(res.error).to.be.an('error');
          expect(res.error.text).to.match(/Error: Metadata error/);
          done();
        });
    });
  });

  it('should throw an error if the chunkSize function is invoked with an error callback', function (done) {
    storage = GridFsStorage({
      url: settings.mongoUrl(),
      chunkSize: (req, file, cb) => {
        cb(new Error('ChunkSize error'));
      }
    });

    const upload = multer({storage});

    app.post('/chunksize', upload.single('photo'), (req, res) => {
      res.send({headers: req.headers, files: req.files, body: req.body});
    });

    storage.on('connection', () => {
      request(app)
        .post('/chunksize')
        .attach('photo', files[0])
        .end((err, res) => {
          expect(res.serverError).to.equal(true);
          expect(res.error).to.be.an('error');
          expect(res.error.text).to.match(/Error: ChunkSize error/);
          done();
        });
    });
  });

  it('should throw an error if the root function is invoked with an error callback', function (done) {

    storage = GridFsStorage({
      url: settings.mongoUrl(),
      root: (req, file, cb) => {
        cb(new Error('Root error'));
      }
    });

    const upload = multer({storage});

    app.post('/root', upload.single('photo'), (req, res) => {
      res.send({headers: req.headers, files: req.files, body: req.body});
    });

    storage.on('connection', () => {
      request(app)
        .post('/root')
        .attach('photo', files[0])
        .end((err, res) => {
          expect(res.serverError).to.equal(true);
          expect(res.error).to.be.an('error');
          expect(res.error.text).to.match(/Error: Root error/);
          done();
        });
    });
  });

  it('should emit an error event when the file streaming fails', function (done) {
    let db, fs;
    const errorSpy = sinon.spy();

    MongoClient
      .connect(settings.mongoUrl())
      .then((_db) => db = _db)
      .then(() => fs = db.collection('fs.files'))
      .then(() => fs.createIndex('name', {unique: true}))
      .then(() => {

        const gfs = new Grid(db, mongo);
        storage = GridFsStorage({
          gfs: gfs,
          filename: (req, file, cb) => {
            cb(null, 'name');
          }
        });

        const upload = multer({storage});

        app.post('/emit', upload.array('photos', 2), (req, res) => {
          res.send({headers: req.headers, files: req.files, body: req.body});
        });

        storage.on('error', errorSpy);

        request(app)
          .post('/emit')
          .attach('photos', files[0])
          .attach('photos', files[1])
          .end((err, body) => {
            expect(body.status).to.equal(500);
            expect(errorSpy).to.be.calledOnce;
            const call = errorSpy.getCall(0);
            expect(call.args[0]).to.be.an.instanceof(Error);
            expect(call.args[1]).to.have.all.keys('chunkSize', 'content_type', 'filename', 'metadata', 'root');
            done();
          });

      });

  });

  it('should fail gracefully if an error is thrown inside an option function', function (done) {

    storage = GridFsStorage({
      url: settings.mongoUrl(),
      filename: () => {
        throw new Error('Filename error');
      }
    });

    const upload = multer({storage});

    app.post('/fail', upload.single('photo'), (req, res) => {
      res.send({headers: req.headers, files: req.files, body: req.body});
    });

    storage.on('connection', () => {
      request(app)
        .post('/fail')
        .attach('photo', files[0])
        .end((err, res) => {
          expect(res.serverError).to.equal(true);
          expect(res.error).to.be.an('error');
          expect(res.error.text).to.match(/Error: Filename error/);
          done();
        });
    });
  });

  it('should fail gracefully if an error is thrown inside a generator function', function (done) {
    if (version.major < 6) {
      this.skip();
    }

    storage = GridFsStorage({
      url: settings.mongoUrl(),
      filename: function*() {
        throw new Error('Filename error');
      }
    });

    const upload = multer({storage});

    app.post('/failgen', upload.single('photo'), (req, res) => {
      res.send({headers: req.headers, files: req.files, body: req.body});
    });

    storage.on('connection', () => {
      request(app)
        .post('/failgen')
        .attach('photo', files[0])
        .end((err, res) => {
          expect(res.serverError).to.equal(true);
          expect(res.error).to.be.an('error');
          expect(res.error.text).to.match(/Error: Filename error/);
          done();
        });
    });
  });

  it('should append the storage error in case the remove function fails', function (done) {
    const e = new Error();
    storage = GridFsStorage({
      url: settings.mongoUrl()
    });

    const upload = multer({storage});

    /* eslint-disable no-unused-vars */
    app.post('/storageerror', upload.single('photo'), (err, req, res, next) => {
      expect(e).to.equal(err);
      expect(e).to.have.property('storageErrors');
    });
    /* eslint-enable no-unused-vars */

    storage.on('connection', () => {
      removeRef = storage.gfs.remove;
      storage.gfs.remove = function (options, callback) {
        callback(new Error());
      };

      request(app)
        .post('/storageerror')
        .attach('photo', files[0])
        .attach('photo', files[1])
        .end((err, res) => {
          expect(res.serverError).to.equal(true);
          expect(res.error).to.be.an('error');
          done();
        });
    });
  });

  it('should throw an error if the mongodb connection fails', function () {
    connectRef = mongo.MongoClient.connect;
    const err = new Error();

    mongo.MongoClient.connect = function (url, cb) {
      cb(err);
    };

    const errFn = function () {
      storage = GridFsStorage({
        url: settings.mongoUrl()
      });
    };

    expect(errFn).to.throw(err);
  });

  after(() => {
    mongo.MongoClient.connect = connectRef;
    unmute();
    storage.gfs.remove = removeRef;
    return cleanDb(storage);
  });

});
<commit_msg>Changed the error event test to a MD5 index<commit_after>'use strict';

const GridFsStorage = require('../index');

const multer = require('multer');
const chai = require('chai');
const expect = chai.expect;
const request = require('supertest');
const express = require('express');
const settings = require('./utils/settings');
const mongo = require('mongodb');
const MongoClient = mongo.MongoClient;
const Grid = require('gridfs-stream');
const {files, cleanDb, version} = require('./utils/testutils');
const mute = require('mute');

const sinon = require('sinon');
const sinonChai = require('sinon-chai');
chai.use(sinonChai);

describe('error handling', function () {
  let storage, app, unmute, connectRef, removeRef;

  before(() => {
    unmute = mute(process.stderr);
    app = express();
  });

  it('should throw an error if the identifier function is invoked with an error callback', function (done) {
    storage = GridFsStorage({
      url: settings.mongoUrl(),
      identifier: (req, file, cb) => {
        cb(new Error('Identifier error'));
      }
    });

    const upload = multer({storage});

    app.post('/identifier', upload.single('photo'), (req, res) => {
      res.send({headers: req.headers, files: req.files, body: req.body});
    });

    storage.on('connection', () => {
      request(app)
        .post('/identifier')
        .attach('photo', files[0])
        .end((err, res) => {
          expect(res.serverError).to.equal(true);
          expect(res.error).to.be.an('error');
          expect(res.error.text).to.match(/Error: Identifier error/);
          done();
        });
    });
  });

  it('should throw an error if the filename function is invoked with an error callback', function (done) {
    storage = GridFsStorage({
      url: settings.mongoUrl(),
      filename: (req, file, cb) => {
        cb(new Error('Filename error'));
      }
    });

    const upload = multer({storage});

    app.post('/filename', upload.single('photo'), (req, res) => {
      res.send({headers: req.headers, files: req.files, body: req.body});
    });

    storage.on('connection', () => {
      request(app)
        .post('/filename')
        .attach('photo', files[0])
        .end((err, res) => {
          expect(res.serverError).to.equal(true);
          expect(res.error).to.be.an('error');
          expect(res.error.text).to.match(/Error: Filename error/);
          done();
        });
    });
  });

  it('should throw an error if the metadata is invoked with an error callback', function (done) {
    storage = GridFsStorage({
      url: settings.mongoUrl(),
      metadata: (req, file, cb) => {
        cb(new Error('Metadata error'));
      }
    });

    const upload = multer({storage});

    app.post('/metadata', upload.single('photo'), (req, res) => {
      res.send({headers: req.headers, files: req.files, body: req.body});
    });

    storage.on('connection', function () {
      request(app)
        .post('/metadata')
        .attach('photo', files[0])
        .end((err, res) => {
          expect(res.serverError).to.equal(true);
          expect(res.error).to.be.an('error');
          expect(res.error.text).to.match(/Error: Metadata error/);
          done();
        });
    });
  });

  it('should throw an error if the chunkSize function is invoked with an error callback', function (done) {
    storage = GridFsStorage({
      url: settings.mongoUrl(),
      chunkSize: (req, file, cb) => {
        cb(new Error('ChunkSize error'));
      }
    });

    const upload = multer({storage});

    app.post('/chunksize', upload.single('photo'), (req, res) => {
      res.send({headers: req.headers, files: req.files, body: req.body});
    });

    storage.on('connection', () => {
      request(app)
        .post('/chunksize')
        .attach('photo', files[0])
        .end((err, res) => {
          expect(res.serverError).to.equal(true);
          expect(res.error).to.be.an('error');
          expect(res.error.text).to.match(/Error: ChunkSize error/);
          done();
        });
    });
  });

  it('should throw an error if the root function is invoked with an error callback', function (done) {

    storage = GridFsStorage({
      url: settings.mongoUrl(),
      root: (req, file, cb) => {
        cb(new Error('Root error'));
      }
    });

    const upload = multer({storage});

    app.post('/root', upload.single('photo'), (req, res) => {
      res.send({headers: req.headers, files: req.files, body: req.body});
    });

    storage.on('connection', () => {
      request(app)
        .post('/root')
        .attach('photo', files[0])
        .end((err, res) => {
          expect(res.serverError).to.equal(true);
          expect(res.error).to.be.an('error');
          expect(res.error.text).to.match(/Error: Root error/);
          done();
        });
    });
  });

  it('should emit an error event when the file streaming fails', function (done) {
    let db, fs;
    const errorSpy = sinon.spy();

    MongoClient
      .connect(settings.mongoUrl())
      .then((_db) => db = _db)
      .then(() => fs = db.collection('fs.files'))
      .then(() => fs.createIndex('md5', {unique: true}))
      .then(() => {

        const gfs = new Grid(db, mongo);
        storage = GridFsStorage({gfs});

        const upload = multer({storage});

        app.post('/emit', upload.array('photos', 2), (req, res) => {
          res.send({headers: req.headers, files: req.files, body: req.body});
        });

        storage.on('error', errorSpy);

        request(app)
          .post('/emit')
          // Send the same file twice so the checksum is the same
          .attach('photos', files[0])
          .attach('photos', files[0])
          .end((err, body) => {
            expect(body.status).to.equal(500);
            expect(errorSpy).to.be.calledOnce;
            const call = errorSpy.getCall(0);
            expect(call.args[0]).to.be.an.instanceof(Error);
            expect(call.args[1]).to.have.all.keys('chunkSize', 'content_type', 'filename', 'metadata', 'root');
            done();
          });

      });

  });

  it('should fail gracefully if an error is thrown inside an option function', function (done) {

    storage = GridFsStorage({
      url: settings.mongoUrl(),
      filename: () => {
        throw new Error('Filename error');
      }
    });

    const upload = multer({storage});

    app.post('/fail', upload.single('photo'), (req, res) => {
      res.send({headers: req.headers, files: req.files, body: req.body});
    });

    storage.on('connection', () => {
      request(app)
        .post('/fail')
        .attach('photo', files[0])
        .end((err, res) => {
          expect(res.serverError).to.equal(true);
          expect(res.error).to.be.an('error');
          expect(res.error.text).to.match(/Error: Filename error/);
          done();
        });
    });
  });

  it('should fail gracefully if an error is thrown inside a generator function', function (done) {
    if (version.major < 6) {
      this.skip();
    }

    storage = GridFsStorage({
      url: settings.mongoUrl(),
      filename: function*() {
        throw new Error('Filename error');
      }
    });

    const upload = multer({storage});

    app.post('/failgen', upload.single('photo'), (req, res) => {
      res.send({headers: req.headers, files: req.files, body: req.body});
    });

    storage.on('connection', () => {
      request(app)
        .post('/failgen')
        .attach('photo', files[0])
        .end((err, res) => {
          expect(res.serverError).to.equal(true);
          expect(res.error).to.be.an('error');
          expect(res.error.text).to.match(/Error: Filename error/);
          done();
        });
    });
  });

  it('should append the storage error in case the remove function fails', function (done) {
    const e = new Error();
    storage = GridFsStorage({
      url: settings.mongoUrl()
    });

    const upload = multer({storage});

    /* eslint-disable no-unused-vars */
    app.post('/storageerror', upload.single('photo'), (err, req, res, next) => {
      expect(e).to.equal(err);
      expect(e).to.have.property('storageErrors');
    });
    /* eslint-enable no-unused-vars */

    storage.on('connection', () => {
      removeRef = storage.gfs.remove;
      storage.gfs.remove = function (options, callback) {
        callback(new Error());
      };

      request(app)
        .post('/storageerror')
        .attach('photo', files[0])
        .attach('photo', files[1])
        .end((err, res) => {
          expect(res.serverError).to.equal(true);
          expect(res.error).to.be.an('error');
          done();
        });
    });
  });

  it('should throw an error if the mongodb connection fails', function () {
    connectRef = mongo.MongoClient.connect;
    const err = new Error();

    mongo.MongoClient.connect = function (url, cb) {
      cb(err);
    };

    const errFn = function () {
      storage = GridFsStorage({
        url: settings.mongoUrl()
      });
    };

    expect(errFn).to.throw(err);
  });

  after(() => {
    mongo.MongoClient.connect = connectRef;
    unmute();
    storage.gfs.remove = removeRef;
    return cleanDb(storage);
  });

});
<|endoftext|>


<commit_before>var RECORD_URL = "http://localhost/record?start=-4&duration=18";
var TWITTER_USERNAME = "GoalFlash";

var twitter = require('twitter')
  , humanize = require('humanize')
  , googl = require('goo.gl')
  , request = require('request');

var twit = new twitter(require('./settings.json').twitter);

var notify = function(url) {
  var text = lastTweet.replace(/http.*/i,'').replace(/^RT @[a-zA-Z]{1,15}:? ?/i,'').replace(/ $/,'');
  googl.shorten(url)
    .then(function (shortUrl) {
      twit.updateStatus(text + " - Video: "+shortUrl, function(data) {
        console.log("notify> ", data);
      });
    })
    .catch(function (err) {
        console.error(err.message);
    });
};

var lastTweet = '';

/* For testing:
setTimeout(function() {
  request(RECORD_URL, function(err, res, body) {
    console.log("body: ", body);
    notify(body);
  });

}, 1000);
*/

console.log(humanize.date("Y-m-d H:i:s")+" Connecting to the Twitter Stream for @"+TWITTER_USERNAME);
twit.stream('user', {track:TWITTER_USERNAME}, function(stream) {
    console.log(humanize.date("Y-m-d H:i:s")+" Connected");
    stream.on('data', function(data) {
      console.log(humanize.date("Y-m-d H:i:s")+" DATA: ", data);
      if(!data.text) return;
      if(data.user.screen_name != TWITTER_USERNAME) return;
      lastTweet = data.text;
      request(RECORD_URL, function(err, res, body) {
        console.log("body: ", body);
        if(body.match(/http/))
          notify(body);
      });
    });

    stream.on('error', function(error) {
      console.error("Error in the twitter stream: ", error);
      process.exit(1);
    });

    stream.on('end', function(error) {
      console.error("Twitter stream ended - exiting");
      process.exit(1);
    });
});
<commit_msg>starting earlier<commit_after>var RECORD_URL = "http://localhost/record?start=-6&duration=18";
var TWITTER_USERNAME = "GoalFlash";

var twitter = require('twitter')
  , humanize = require('humanize')
  , googl = require('goo.gl')
  , request = require('request');

var twit = new twitter(require('./settings.json').twitter);

var notify = function(url) {
  var text = lastTweet.replace(/http.*/i,'').replace(/^RT @[a-zA-Z]{1,15}:? ?/i,'').replace(/ $/,'');
  googl.shorten(url)
    .then(function (shortUrl) {
      twit.updateStatus(text + " - Video: "+shortUrl, function(data) {
        console.log("notify> ", data);
      });
    })
    .catch(function (err) {
        console.error(err.message);
    });
};

var lastTweet = '';

/* For testing:
setTimeout(function() {
  request(RECORD_URL, function(err, res, body) {
    console.log("body: ", body);
    notify(body);
  });

}, 1000);
*/

console.log(humanize.date("Y-m-d H:i:s")+" Connecting to the Twitter Stream for @"+TWITTER_USERNAME);
twit.stream('user', {track:TWITTER_USERNAME}, function(stream) {
    console.log(humanize.date("Y-m-d H:i:s")+" Connected");
    stream.on('data', function(data) {
      console.log(humanize.date("Y-m-d H:i:s")+" DATA: ", data);
      if(!data.text) return;
      if(data.user.screen_name != TWITTER_USERNAME) return;
      lastTweet = data.text;
      request(RECORD_URL, function(err, res, body) {
        console.log("body: ", body);
        if(body.match(/http/))
          notify(body);
      });
    });

    stream.on('error', function(error) {
      console.error("Error in the twitter stream: ", error);
      process.exit(1);
    });

    stream.on('end', function(error) {
      console.error("Twitter stream ended - exiting");
      process.exit(1);
    });
});
<|endoftext|>


<commit_before>/* globals process */
'use strict'

const express = require('express')
const app = express()
const bodyParser = require('body-parser')

const twitterParse = require('twitter-url-parser')
const twitterAPI = require('node-twitter-api')
const twitter = new twitterAPI({
  consumerKey: process.env.TWITTER_CONSUMER_KEY,
  consumerSecret: process.env.TWITTER_CONSUMER_SECRET,
  callback: process.env.TWITTER_OAUTH_CALLBACK || 'http://localhost:8080/oauth'
})

const redis = require('redis')
const client = redis.createClient(process.env.REDIS_URL)

const bigInt = require('big-integer')

client.on('error', (error) => {
  console.log(error)
})

app.use(bodyParser.json())

app.post('/', (req, res) => {
  const twitterId = twitterParse(req.body.link).id
  client.hgetall('access', (error, access) => {
    if (error) {
      console.log(error)
    } else if (access) {
      console.log('t: ' + access.token)
      console.log('ts: ' + access.tokenSecret)
      twitter.search(
        {
          q: req.body.text,
          max_id: bigInt(twitterId).minus(1).toString()
        },
        access.token,
        access.tokenSecret,
        (error, data, response) => {
          if (error) {
            console.log(error)
          }
          else if (data && data.statuses && data.statuses[0]) {
            twitter.statuses(
              'update',
              {
                status: 'ney ' + data.statuses[0].id_str // '@horse_js @' + data.statuses[0].user.screen_name + ' https://twitter.com/' + data.statuses[0].user.screen_name + '/status/' + data.statuses[0].id_str,
              // in_reply_to_status_id: twitterId
              },
              access.token,
              access.tokenSecret,
              (error, data, response) => {
                if (error) {
                  console.log(error)
                }
              }
            )
          }
        })
    }
  })
  res.end()
})

app.get('/authenticate', (req, res) => {
  twitter.getRequestToken((error, requestToken, requestTokenSecret, results) => {
    if (error) {
      console.log('Error getting OAuth request token : ' + error)
    } else {
      client.del('request');
      client.hmset('request', 'token', requestToken, 'tokenSecret', requestTokenSecret, (error, result) => {
        if (error) {
          console.log(error)
        } else {
          res.redirect(twitter.getAuthUrl(requestToken))
        }
      })

    }
  })
})

app.get('/oauth', (req, res) => {
  client.hgetall('request', (error, request) => {
    if (error) {
      console.log(error)
    } else if (request) {
      console.log('t: ' + request.token)
      console.log('ts: ' + request.tokenSecret)
      twitter.getAccessToken(
        request.token,
        request.tokenSecret,
        req.query.oauth_verifier,
        (error, accessToken, accessTokenSecret, results) => {
          if (error) {
            console.log(error)
          } else {
            twitter.verifyCredentials(
              accessToken,
              accessTokenSecret,
              {},
              (error, data, response) => {
                if (error) {
                  console.log(error)
                } else {
                  client.del('access');
                  client.hmset('access', 'token', accessToken, 'tokenSecret', accessTokenSecret, (error, result) => {
                    if (error) {
                      console.log(error)
                    } else {
                      res.send(data['screen_name'])
                    }
                  })
                }
              })
          }
        })
    }
  })
})

app.listen(process.env.PORT || 8080, () => console.log('listening ' + (process.env.PORT || 8080)))
<commit_msg>go live again<commit_after>/* globals process */
'use strict'

// other
const bigInt = require('big-integer')

// twitter api
const twitterParse = require('twitter-url-parser')
const twitterAPI = require('node-twitter-api')
const twitter = new twitterAPI({
  consumerKey: process.env.TWITTER_CONSUMER_KEY,
  consumerSecret: process.env.TWITTER_CONSUMER_SECRET,
  callback: process.env.TWITTER_OAUTH_CALLBACK || 'http://localhost:8080/oauth'
})

// redis
const redis = require('redis')
const client = redis.createClient(process.env.REDIS_URL)

client.on('error', (error) => {
  console.log(error)
})

// express
const express = require('express')
const app = express()
const bodyParser = require('body-parser')

app.use(bodyParser.json())

app.post('/', (req, res) => {
  const twitterId = twitterParse(req.body.link).id
  client.hgetall('access', (error, access) => {
    if (error) {
      console.log(error)
    } else if (access) {
      twitter.search(
        {
          q: req.body.text,
          max_id: bigInt(twitterId).minus(1).toString()
        },
        access.token,
        access.tokenSecret,
        (error, data, response) => {
          if (error) {
            console.log(error)
          }
          else if (data && data.statuses && data.statuses[0]) {
            twitter.statuses(
              'update',
              {
                status: '@horse_js @' + data.statuses[0].user.screen_name + ' https://twitter.com/' + data.statuses[0].user.screen_name + '/status/' + data.statuses[0].id_str,
                in_reply_to_status_id: twitterId
              },
              access.token,
              access.tokenSecret,
              (error, data, response) => {
                if (error) {
                  console.log(error)
                }
              }
            )
          }
        })
    }
  })
  res.end()
})

app.get('/authenticate', (req, res) => {
  twitter.getRequestToken((error, requestToken, requestTokenSecret, results) => {
    if (error) {
      console.log('Error getting OAuth request token : ' + error)
    } else {
      client.del('request')
      client.hmset('request', 'token', requestToken, 'tokenSecret', requestTokenSecret, (error, result) => {
        if (error) {
          console.log(error)
        } else {
          res.redirect(twitter.getAuthUrl(requestToken))
        }
      })

    }
  })
})

app.get('/oauth', (req, res) => {
  client.hgetall('request', (error, request) => {
    if (error) {
      console.log(error)
    } else if (request) {
      twitter.getAccessToken(
        request.token,
        request.tokenSecret,
        req.query.oauth_verifier,
        (error, accessToken, accessTokenSecret, results) => {
          if (error) {
            console.log(error)
          } else {
            twitter.verifyCredentials(
              accessToken,
              accessTokenSecret,
              {},
              (error, data, response) => {
                if (error) {
                  console.log(error)
                } else {
                  client.del('access')
                  client.hmset('access', 'token', accessToken, 'tokenSecret', accessTokenSecret, (error, result) => {
                    if (error) {
                      console.log(error)
                    } else {
                      res.send(data['screen_name'])
                    }
                  })
                }
              })
          }
        })
    }
  })
})

app.listen(process.env.PORT || 8080, () => console.log('listening ' + (process.env.PORT || 8080)))
<|endoftext|>


<commit_before>var sensors = require("../lib/ev3/sensors");
var Adapter = require("../lib/ev3/adapter");
var runIfSp = require("./util").runIfSp;

suite("Ev3 sensors", function(){

  test("should get touch sensor data", runIfSp(function(done){
    var a = new Adapter(process.env.SERIAL_PORT);
    var s = sensors.TouchSensor(a, 1);
    s.read(function(err, val){
      console.log("touch value: "+val);
      done();
    })
  }))

  test("should get color sensor data", runIfSp(function(done){
    var a = new Adapter(process.env.SERIAL_PORT);
    var s = sensors.ColorSensor(a, 2, ColorSensor.modes.RINTENSITY);
    s.read(function(err, val){
      console.log("color value: "+val);
      done();
    })
  }))

  test("should get IR sensor data", runIfSp(function(done){
    var a = new Adapter(process.env.SERIAL_PORT);
    var s = sensors.InfraSensor(a, 3);
    s.read(function(err, val){
      console.log("ir value: "+val);
      done();
    })
  }))
});<commit_msg>Fixed some bugs in the sensors test<commit_after>var sensors = require("../lib/ev3/sensors");
var Adapter = require("../lib/ev3/adapter");
var runIfSp = require("./util").runIfSp;

suite("Ev3 sensors", function(){

  test("should get touch sensor data", runIfSp(function(done){
    var a = new Adapter(process.env.SERIAL_PORT);
    var s = new sensors.TouchSensor(a, 1);
    s.read(function(err, val){
      console.log("touch value: "+val);
      done();
    })
  }))

  test("should get color sensor data", runIfSp(function(done){
    var a = new Adapter(process.env.SERIAL_PORT);
    var s = new sensors.ColorSensor(a, 2, sensors.ColorSensor.modes.RINTENSITY);
    s.read(function(err, val){
      console.log("color value: "+val);
      done();
    })
  }))

  test("should get IR sensor data", runIfSp(function(done){
    var a = new Adapter(process.env.SERIAL_PORT);
    var s = new sensors.InfraSensor(a, 3);
    s.read(function(err, val){
      console.log("ir value: "+val);
      done();
    })
  }))
});<|endoftext|>


<commit_before>var tessel = require('tessel');
var climateLib = require('climate-si7005');
var wifi = require('wifi-cc3000');
var http = require('http');

// Config
var CLIMATE_PORT = 'A';
var TEMP_UNIT = 'F';
var FREQUENCY = 5 * 60 * 1000;
var MAX_STORAGE = 2048;

// In-memory storage (array of arrays with 0=time, 1=temp, 2=humidity)
var storage = [];

// Climate module

var climate = climateLib.use(tessel.port[CLIMATE_PORT]);

climate.on('ready', function () {
  console.log('Connected to climate module');

  // Loop forever
  setImmediate(function loop () {
    climate.readTemperature(TEMP_UNIT.toLowerCase(), function (err, temperature) {
      climate.readHumidity(function (err, humidity) {
        var currentTime = Date.now()

        storage.push([currentTime, temperature, humidity]);
        if (storage.length > MAX_STORAGE) {
          storage.shift();
        }

        console.log('Temperature:', temperature.toFixed(4) + TEMP_UNIT.toUpperCase(), 'Humidity:', humidity.toFixed(4) + '%RH');

        setTimeout(loop, FREQUENCY);
      });
    });
  });
});

climate.on('error', function(err) {
  console.log('Error connecting to climate module', err);
});

// Web server

function setupServer() {
  http.createServer(function(req, res) {
    if (req.method === 'GET' && req.url === '/') {
      console.log('Serving webpage');

      res.writeHead(200, {'Content-Type': 'text/plain'});
      res.write('Time, Temperature (F), Humidity (%RH)\n');
      storage.forEach(function(data) {
        res.write(data[0] + ', ' + data[1].toFixed(2) + ', ' + data[2].toFixed(2) + '\n');
      });

      res.end();
    } else {
      console.log('Unknown webpage request');

      res.writeHead(404, {'Content-Type': 'text/plain'});
      res.end('Not found\n');
    }
  }).listen(80);
}

if (wifi.isConnected()) {
  setupServer();
} else {
  wifi.on('connect', setupServer);
}
<commit_msg>Add missing semicolon :)<commit_after>var tessel = require('tessel');
var climateLib = require('climate-si7005');
var wifi = require('wifi-cc3000');
var http = require('http');

// Config
var CLIMATE_PORT = 'A';
var TEMP_UNIT = 'F';
var FREQUENCY = 5 * 60 * 1000;
var MAX_STORAGE = 2048;

// In-memory storage (array of arrays with 0=time, 1=temp, 2=humidity)
var storage = [];

// Climate module

var climate = climateLib.use(tessel.port[CLIMATE_PORT]);

climate.on('ready', function () {
  console.log('Connected to climate module');

  // Loop forever
  setImmediate(function loop () {
    climate.readTemperature(TEMP_UNIT.toLowerCase(), function (err, temperature) {
      climate.readHumidity(function (err, humidity) {
        var currentTime = Date.now();

        storage.push([currentTime, temperature, humidity]);
        if (storage.length > MAX_STORAGE) {
          storage.shift();
        }

        console.log('Temperature:', temperature.toFixed(4) + TEMP_UNIT.toUpperCase(), 'Humidity:', humidity.toFixed(4) + '%RH');

        setTimeout(loop, FREQUENCY);
      });
    });
  });
});

climate.on('error', function(err) {
  console.log('Error connecting to climate module', err);
});

// Web server

function setupServer() {
  http.createServer(function(req, res) {
    if (req.method === 'GET' && req.url === '/') {
      console.log('Serving webpage');

      res.writeHead(200, {'Content-Type': 'text/plain'});
      res.write('Time, Temperature (F), Humidity (%RH)\n');
      storage.forEach(function(data) {
        res.write(data[0] + ', ' + data[1].toFixed(2) + ', ' + data[2].toFixed(2) + '\n');
      });

      res.end();
    } else {
      console.log('Unknown webpage request');

      res.writeHead(404, {'Content-Type': 'text/plain'});
      res.end('Not found\n');
    }
  }).listen(80);
}

if (wifi.isConnected()) {
  setupServer();
} else {
  wifi.on('connect', setupServer);
}
<|endoftext|>


<commit_before>define(['lodash', 'filters/lstring', 'directives/file','../meeting-document'], function(_) {

    var MIMES = {
        'application/pdf':                                                            { title: 'PDF',               color: 'red',    btn: 'btn-danger',  icon: 'fa-file-pdf-o'   },
        'application/vnd.openxmlformats-officedocument.wordprocessingml.document' :   { title: 'Word (docx)',       color: 'blue',   btn: 'btn-primary', icon: 'fa-file-word-o'  },
        'application/msword':                                                         { title: 'Word (doc)',        color: 'blue',   btn: 'btn-primary', icon: 'fa-file-word-o'  },
        'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' :         { title: 'Excel (xlsx)',      color: 'green',  btn: 'btn-success', icon: 'fa-file-excel-o' },
        'application/vnd.ms-excel':                                                   { title: 'Excel (xls)',       color: 'green',  btn: 'btn-success', icon: 'fa-file-excel-o' },
        'application/vnd.openxmlformats-officedocument.presentationml.presentation' : { title: 'PowerPoint (pptx)', color: 'orange', btn: 'btn-warning', icon: 'fa-file-powerpoint-o' },
        'application/vnd.ms-powerpoint':                                              { title: 'PowerPoint (ppt)',  color: 'orange', btn: 'btn-warning', icon: 'fa-file-powerpoint-o' },
        'application/zip':                                                            { title: 'Zip',               color: '',       btn: 'btn-default', icon: 'fa-file-archive-o' },
        'text/html':                                                                  { title: 'Web',               color: '',       btn: 'btn-default', icon: 'fa-link' },
        'default':                                                                    { title: '',                  color: 'orange', btn: 'btn-default', icon: 'fa-file-o' }
    };

    var LANGUAGES = {
         ar : "Arabic",
         en : "English",
         es : "Spanish",
         fr : "French",
         ru : "Russian",
         zh : "Chinese"
     };

	return ["$scope", "$route", "$http", '$location', '$q', function ($scope, $route, $http, $location, $q) {

        $scope.FILETYPES  = MIMES;
        $scope.LANGUAGES  = LANGUAGES;

        var documentId = $route.current.params.id;
        var meetingId  = $route.current.params.meeting;

        var _ctrl = $scope.editCtrl = this;
        var document_bak;

        _ctrl.addItem     = addItem;
        _ctrl.removeItem  = removeItem;
        _ctrl.removeFile  = removeFile;
        _ctrl.upload      = upload;
        _ctrl.fileCache   = {};

        _ctrl.clearErrors = clearErrors;
        _ctrl.save   = save;
        _ctrl.close  = close;

        load();

        //==============================
        //
        //==============================
        function load() {

            clearErrors();

            $http.get('/api/v2016/meetings/'+meetingId).then(function(res) {

                _ctrl.meeting = res.data;

                if(documentId=='new')
                    return { };

                return $http.get('/api/v2016/meetings/'+meetingId+'/documents/'+documentId).then(function(res) {
                    return res.data;
                });

            }).then(function(document) {

                if(document.metadata && document.metadata.message)
                    document.metadata.message.level = document.metadata.message.level || "";

                document_bak          = _.cloneDeep(document); //fullclone
                document_bak.metadata = document_bak.metadata || { force:1 };

                _ctrl.document       = document;
                _ctrl.document.files = document.files || [];

                initFiles();

            }).catch(function(err) {
                _ctrl.error = err.data || err;
                console.error(err);
            });
        }

        //==============================
        //
        //==============================
        function save() {

            var oldDoc = prepareData(document_bak);
            var newDoc = prepareData(_ctrl.document);

            var fileIds       = _(_ctrl.document.files).map('_id').compact().uniq().value();

            var filesToCreate = _(_ctrl.document.files||[]).filter(function(f) { return !f._id;                   }).value();
            var filesToDelete = _(document_bak  .files||[]).filter(function(f) { return !~fileIds.indexOf(f._id); }).value();

            var req = $q.resolve(newDoc._id);

            var hasChange = !newDoc._id || JSON.stringify(newDoc) != JSON.stringify(oldDoc);

            if(hasChange) { // update only if any chnages;

                req = $http({
                    data   : newDoc,
                    method : newDoc._id ? 'PUT' : 'POST',
                    url    : newDoc._id ? '/api/v2016/meetings/'+meetingId+'/documents/'+newDoc._id :
                                          '/api/v2016/meetings/'+meetingId+'/documents'
                }).then(function(res) {

                    return (_ctrl.document._id = _ctrl.document._id || res.data._id);

                });
            }

            req.then(function(docId){

                var delQ = _.map(filesToDelete, function(f){
                    return $http.delete('/api/v2016/documents/'+docId+'/files/'+f._id);
                });

                var newQ = _.map(filesToCreate, function(f){
                    return $http.post('/api/v2016/documents/'+docId+'/files', { url : f.url });
                });

                return $q.all(delQ.concat(newQ));

            }).then(function(){

                close();

            }).catch(function(err) {
                _ctrl.error = err.data || err;
                console.error(err);
            });
        }

        //==============================
        //
        //==============================
        function prepareData(document) {

            var doc = {
                _id:         document._id,
                symbol:      document.symbol || undefined,
                type:        document.type,
                group:       document.group  || undefined,
                date:        document.date   || new Date(),
                agendaItems: document.agendaItems,
                title:       document.title,
                description: document.description,
                metadata:    _.cloneDeep(document.metadata||{}),
            };

            doc.metadata.patterns = _(document.files).map('name').map(parseFilename).map('prefix').uniq().sort().value();

            if(doc.metadata.message)
                doc.metadata.message.level = doc.metadata.message.level || null;

            return doc;
        }

        //==============================
        //
        //==============================
        function upload(file, force) {

            clearErrors();

            if(!file) return;

            var type     = file.type;
            var language = parseFilename(file.name).language;

            // SANITIZE

            if(!/^[a-z0-9][a-z0-9_\-\.]{7,127}$/i.test(file.name)) {
                _ctrl.fileError = {
                    code: "FILENAME",
                    new: {  name: file.name, type: file.type, language: language }
                };
                return;
            }

            if(!language) {
                _ctrl.fileError = {
                    code: "LANGUAGE",
                    new: {  name: file.name, type: file.type, language: language }
                };
                return;
            }

            var oldFile =  _ctrl.fileCache[type] && _ctrl.fileCache[type][language];

            if(oldFile && force) {

                _ctrl.document.files = _.without(_ctrl.document.files, oldFile);

                oldFile = null;

                initFiles();
            }

            if(oldFile) {
                _ctrl.fileError = {
                    code: "EXISTS",
                    old: _ctrl.fileCache[type][language],
                    new: { name: file.name, type: file.type, language: language }
                };
                return;
            }

            // DO UPLOAD

            var target;

            $scope.uploading = true;
            $scope.uploadProgress = 0;

            return $http.post('/api/v2015/temporary-files', { filename : file.name }).then(function(res) {

                target = res.data;

                return $http.put(target.url, file, {
                    headers : { 'Content-Type' : target.contentType },
                    uploadEventHandlers: { progress: uploadProgress }
                });

            }).then(function() {

                return $http.get('/api/v2015/temporary-files/'+target.uid);

            }).then(function(res) {

                var fileInfo = {
                    name: res.data.filename,
                    type: res.data.contentType,
                    size: res.data.contentLength,
                    url: 'upload://'+target.uid,
                    language: language
                };

                _ctrl.document.files.push(fileInfo);

                initFiles();

            }).catch(function(err) {
                _ctrl.error = err.data || err;
                console.error(err);
            }).finally(function(){
                delete $scope.uploading;
                delete $scope.uploadProgress;
            });

        }

        //==============================
        //
        //==============================
        function uploadProgress(e) {
            $scope.$applyAsync(function() {
                $scope.uploadProgress = (e.loaded*100/e.total)|0;
            });
        }

        //==============================
        //
        //==============================
        function parseFilename(filename) {

            filename = filename.toLowerCase();

            var matches = filename.match(/-([a-z]{2})\.[a-z]+$/i);

            return {
                name     : filename,
                prefix   : filename.replace(/-[a-z]{2}\.[a-z]+$/i, ''),
                language : matches && matches[1].toLowerCase()
            };
        }

        //==============================
        //
        //==============================
        function initFiles() {

            var files = _ctrl.document.files;

            _ctrl.fileCache = {
                languages : _(files).map('language').uniq().sort().value(),
                types     : _(files).map('type'    ).uniq().sort().value(),
                names     : _(files).map('name'    ).uniq().sort().value(),
                prefixes  : _(files).map('name'    ).map(parseFilename).map('prefix').uniq().sort().value()
            };

            _.forEach(files, function(f){
                _ctrl.fileCache[f.type] = _ctrl.fileCache[f.type] || {};
                _ctrl.fileCache[f.type][f.language] = f;
            });
        }

        //==============================
        //
        //==============================
        function addItem(item) {
            item = Number(item);
            _ctrl.document.agendaItems = _(_ctrl.document.agendaItems||[]).union([item]).uniq().sort(_.identity).value();
        }

        //==============================
        //
        //==============================
        function removeItem(item) {
            _ctrl.document.agendaItems = _(_ctrl.document.agendaItems||[]).filter(function(i){ return i!=item; }).uniq().sort(_.identity).value();
            if(!_ctrl.document.agendaItems.length)
                delete _ctrl.document.agendaItems;
        }

        //==============================
        //
        //==============================
        function removeFile(file) {

            _ctrl.document.files = _(_ctrl.document.files||[]).without(file).value();
            initFiles();
        }

        //==============================
        //
        //==============================
        function close() {

            if(_ctrl.document._id) {
                $location.search({ tabFor: _ctrl.document._id});
            }

            if(meetingId=="COP13-HLS") return $location.path('/2016/cop-13-hls/documents');
            if(meetingId=="COP-13")    return $location.path('/2016/cop-13/documents');
            if(meetingId=="MOP-08")    return $location.path('/2016/cp-mop-8/documents');
            if(meetingId=="NP-MOP-02") return $location.path('/2016/np-mop-2/documents');

            $location.path('/');
        }

        //==============================
        //
        //==============================
        function clearErrors() {
            delete _ctrl.error;
            delete _ctrl.fileError;
        }
	}];
});
<commit_msg>Fix force update<commit_after>define(['lodash', 'filters/lstring', 'directives/file','../meeting-document'], function(_) {

    var MIMES = {
        'application/pdf':                                                            { title: 'PDF',               color: 'red',    btn: 'btn-danger',  icon: 'fa-file-pdf-o'   },
        'application/vnd.openxmlformats-officedocument.wordprocessingml.document' :   { title: 'Word (docx)',       color: 'blue',   btn: 'btn-primary', icon: 'fa-file-word-o'  },
        'application/msword':                                                         { title: 'Word (doc)',        color: 'blue',   btn: 'btn-primary', icon: 'fa-file-word-o'  },
        'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' :         { title: 'Excel (xlsx)',      color: 'green',  btn: 'btn-success', icon: 'fa-file-excel-o' },
        'application/vnd.ms-excel':                                                   { title: 'Excel (xls)',       color: 'green',  btn: 'btn-success', icon: 'fa-file-excel-o' },
        'application/vnd.openxmlformats-officedocument.presentationml.presentation' : { title: 'PowerPoint (pptx)', color: 'orange', btn: 'btn-warning', icon: 'fa-file-powerpoint-o' },
        'application/vnd.ms-powerpoint':                                              { title: 'PowerPoint (ppt)',  color: 'orange', btn: 'btn-warning', icon: 'fa-file-powerpoint-o' },
        'application/zip':                                                            { title: 'Zip',               color: '',       btn: 'btn-default', icon: 'fa-file-archive-o' },
        'text/html':                                                                  { title: 'Web',               color: '',       btn: 'btn-default', icon: 'fa-link' },
        'default':                                                                    { title: '',                  color: 'orange', btn: 'btn-default', icon: 'fa-file-o' }
    };

    var LANGUAGES = {
         ar : "Arabic",
         en : "English",
         es : "Spanish",
         fr : "French",
         ru : "Russian",
         zh : "Chinese"
     };

	return ["$scope", "$route", "$http", '$location', '$q', function ($scope, $route, $http, $location, $q) {

        $scope.FILETYPES  = MIMES;
        $scope.LANGUAGES  = LANGUAGES;

        var documentId = $route.current.params.id;
        var meetingId  = $route.current.params.meeting;

        var _ctrl = $scope.editCtrl = this;
        var document_bak;
        var forceUpdate;

        _ctrl.addItem     = addItem;
        _ctrl.removeItem  = removeItem;
        _ctrl.removeFile  = removeFile;
        _ctrl.upload      = upload;
        _ctrl.fileCache   = {};

        _ctrl.clearErrors = clearErrors;
        _ctrl.save   = save;
        _ctrl.close  = close;

        load();

        //==============================
        //
        //==============================
        function load() {

            clearErrors();

            $http.get('/api/v2016/meetings/'+meetingId).then(function(res) {

                _ctrl.meeting = res.data;

                if(documentId=='new')
                    return { };

                return $http.get('/api/v2016/meetings/'+meetingId+'/documents/'+documentId).then(function(res) {
                    return res.data;
                });

            }).then(function(document) {

                if(document.metadata && document.metadata.message)
                    document.metadata.message.level = document.metadata.message.level || "";

                document_bak = _.cloneDeep(document); //fullclone

                _ctrl.document       = document;
                _ctrl.document.files = document.files || [];

                forceUpdate = document.files.length && !((document.metadata||{}).patterns||[]).length;

                initFiles();

            }).catch(function(err) {
                _ctrl.error = err.data || err;
                console.error(err);
            });
        }

        //==============================
        //
        //==============================
        function save() {

            var oldDoc = prepareData(document_bak);
            var newDoc = prepareData(_ctrl.document);

            var fileIds       = _(_ctrl.document.files).map('_id').compact().uniq().value();

            var filesToCreate = _(_ctrl.document.files||[]).filter(function(f) { return !f._id;                   }).value();
            var filesToDelete = _(document_bak  .files||[]).filter(function(f) { return !~fileIds.indexOf(f._id); }).value();

            var req = $q.resolve(newDoc._id);

            var hasChange = forceUpdate || !newDoc._id || JSON.stringify(newDoc) != JSON.stringify(oldDoc);

            if(hasChange) { // update only if any chnages;

                req = $http({
                    data   : newDoc,
                    method : newDoc._id ? 'PUT' : 'POST',
                    url    : newDoc._id ? '/api/v2016/meetings/'+meetingId+'/documents/'+newDoc._id :
                                          '/api/v2016/meetings/'+meetingId+'/documents'
                }).then(function(res) {

                    return (_ctrl.document._id = _ctrl.document._id || res.data._id);

                });
            }

            req.then(function(docId){

                var delQ = _.map(filesToDelete, function(f){
                    return $http.delete('/api/v2016/documents/'+docId+'/files/'+f._id);
                });

                var newQ = _.map(filesToCreate, function(f){
                    return $http.post('/api/v2016/documents/'+docId+'/files', { url : f.url });
                });

                return $q.all(delQ.concat(newQ));

            }).then(function(){

                close();

            }).catch(function(err) {
                _ctrl.error = err.data || err;
                console.error(err);
            });
        }

        //==============================
        //
        //==============================
        function prepareData(document) {

            var doc = {
                _id:         document._id,
                symbol:      document.symbol || undefined,
                type:        document.type,
                group:       document.group  || undefined,
                date:        document.date   || new Date(),
                agendaItems: document.agendaItems,
                title:       document.title,
                description: document.description,
                metadata:    _.cloneDeep(document.metadata||{}),
            };

            doc.metadata.patterns = _(document.files).map('name').map(parseFilename).map('prefix').uniq().sort().value();

            if(doc.metadata.message)
                doc.metadata.message.level = doc.metadata.message.level || null;

            return doc;
        }

        //==============================
        //
        //==============================
        function upload(file, force) {

            clearErrors();

            if(!file) return;

            var type     = file.type;
            var language = parseFilename(file.name).language;

            // SANITIZE

            if(!/^[a-z0-9][a-z0-9_\-\.]{7,127}$/i.test(file.name)) {
                _ctrl.fileError = {
                    code: "FILENAME",
                    new: {  name: file.name, type: file.type, language: language }
                };
                return;
            }

            if(!language) {
                _ctrl.fileError = {
                    code: "LANGUAGE",
                    new: {  name: file.name, type: file.type, language: language }
                };
                return;
            }

            var oldFile =  _ctrl.fileCache[type] && _ctrl.fileCache[type][language];

            if(oldFile && force) {

                _ctrl.document.files = _.without(_ctrl.document.files, oldFile);

                oldFile = null;

                initFiles();
            }

            if(oldFile) {
                _ctrl.fileError = {
                    code: "EXISTS",
                    old: _ctrl.fileCache[type][language],
                    new: { name: file.name, type: file.type, language: language }
                };
                return;
            }

            // DO UPLOAD

            var target;

            $scope.uploading = true;
            $scope.uploadProgress = 0;

            return $http.post('/api/v2015/temporary-files', { filename : file.name }).then(function(res) {

                target = res.data;

                return $http.put(target.url, file, {
                    headers : { 'Content-Type' : target.contentType },
                    uploadEventHandlers: { progress: uploadProgress }
                });

            }).then(function() {

                return $http.get('/api/v2015/temporary-files/'+target.uid);

            }).then(function(res) {

                var fileInfo = {
                    name: res.data.filename,
                    type: res.data.contentType,
                    size: res.data.contentLength,
                    url: 'upload://'+target.uid,
                    language: language
                };

                _ctrl.document.files.push(fileInfo);

                initFiles();

            }).catch(function(err) {
                _ctrl.error = err.data || err;
                console.error(err);
            }).finally(function(){
                delete $scope.uploading;
                delete $scope.uploadProgress;
            });

        }

        //==============================
        //
        //==============================
        function uploadProgress(e) {
            $scope.$applyAsync(function() {
                $scope.uploadProgress = (e.loaded*100/e.total)|0;
            });
        }

        //==============================
        //
        //==============================
        function parseFilename(filename) {

            filename = filename.toLowerCase();

            var matches = filename.match(/-([a-z]{2})\.[a-z]+$/i);

            return {
                name     : filename,
                prefix   : filename.replace(/-[a-z]{2}\.[a-z]+$/i, ''),
                language : matches && matches[1].toLowerCase()
            };
        }

        //==============================
        //
        //==============================
        function initFiles() {

            var files = _ctrl.document.files;

            _ctrl.fileCache = {
                languages : _(files).map('language').uniq().sort().value(),
                types     : _(files).map('type'    ).uniq().sort().value(),
                names     : _(files).map('name'    ).uniq().sort().value(),
                prefixes  : _(files).map('name'    ).map(parseFilename).map('prefix').uniq().sort().value()
            };

            _.forEach(files, function(f){
                _ctrl.fileCache[f.type] = _ctrl.fileCache[f.type] || {};
                _ctrl.fileCache[f.type][f.language] = f;
            });
        }

        //==============================
        //
        //==============================
        function addItem(item) {
            item = Number(item);
            _ctrl.document.agendaItems = _(_ctrl.document.agendaItems||[]).union([item]).uniq().sort(_.identity).value();
        }

        //==============================
        //
        //==============================
        function removeItem(item) {
            _ctrl.document.agendaItems = _(_ctrl.document.agendaItems||[]).filter(function(i){ return i!=item; }).uniq().sort(_.identity).value();
            if(!_ctrl.document.agendaItems.length)
                delete _ctrl.document.agendaItems;
        }

        //==============================
        //
        //==============================
        function removeFile(file) {

            _ctrl.document.files = _(_ctrl.document.files||[]).without(file).value();
            initFiles();
        }

        //==============================
        //
        //==============================
        function close() {

            if(_ctrl.document._id) {
                $location.search({ tabFor: _ctrl.document._id});
            }

            if(meetingId=="COP13-HLS") return $location.path('/2016/cop-13-hls/documents');
            if(meetingId=="COP-13")    return $location.path('/2016/cop-13/documents');
            if(meetingId=="MOP-08")    return $location.path('/2016/cp-mop-8/documents');
            if(meetingId=="NP-MOP-02") return $location.path('/2016/np-mop-2/documents');

            $location.path('/');
        }

        //==============================
        //
        //==============================
        function clearErrors() {
            delete _ctrl.error;
            delete _ctrl.fileError;
        }
	}];
});
<|endoftext|>


<commit_before>// Copyright 2020, University of Colorado Boulder

/**
 * A dialog with a few basic UI components with self-voicing and gesture control to give
 * the user a chance to play with components before interacting with them in the simulation.
 * This is intended for use only in prototypes for user interviews. The hope is that providing
 * user with a chance to play with components before jumping into the sim will separate
 * learning of the controls versus learning of the content.
 *
 * @author Jesse Greenberg
 */

import NumberProperty from '../../../../axon/js/NumberProperty.js';
import Range from '../../../../dot/js/Range.js';
import Utils from '../../../../dot/js/Utils.js';
import StringUtils from '../../../../phetcommon/js/util/StringUtils.js';
import merge from '../../../../phet-core/js/merge.js';
import Display from '../../../../scenery/js/display/Display.js';
import HBox from '../../../../scenery/js/nodes/HBox.js';
import RichText from '../../../../scenery/js/nodes/RichText.js';
import Text from '../../../../scenery/js/nodes/Text.js';
import VBox from '../../../../scenery/js/nodes/VBox.js';
import TextPushButton from '../../../../sun/js/buttons/TextPushButton.js';
import Dialog from '../../../../sun/js/Dialog.js';
import HSlider from '../../../../sun/js/HSlider.js';
import SelfVoicingUtterance from '../../../../utterance-queue/js/SelfVoicingUtterance.js';
import PhetFont from '../../PhetFont.js';
import sceneryPhet from '../../sceneryPhet.js';
import sceneryPhetStrings from '../../sceneryPhetStrings.js';
import SelfVoicingInputListener from './SelfVoicingInputListener.js';
import SelfVoicingWrapperNode from './SelfVoicingWrapperNode.js';

// constants
const contentFont = new PhetFont( 16 );
const titleFont = new PhetFont( 32 );

const titleString = 'Gestures';
const dialogDescriptionString = 'Welcome! This simulation uses web speech and some custom gestures, here are a couple ' +
                                'to test out to get things going. When ready, continue to simulation.';
const continueButtonContent = 'Continue to simulation';
const testButtonContent = 'Activate Me!';
const pressedString = 'Thanks for activating me! You rock!';
const sliderLabelString = 'Move Me';
const grabDragHintPatternString = sceneryPhetStrings.a11y.selfVoicing.grabDragHintPattern;
const grabbedAlertString = sceneryPhetStrings.a11y.selfVoicing.grabbedAlert;
const releasedString = sceneryPhetStrings.a11y.grabDrag.released;

class SelfVoicingIntroDialog extends Dialog {
  constructor( options ) {
    if ( options ) {
      assert && assert( options.title === undefined, 'SelfVoicingIntroDialog sets title' );
    }

    options = merge( {
      title: new Text( titleString, {
        font: titleFont,
        tagName: 'h1',
        innerContent: titleString
      } )
    }, options );

    const descriptionParagraph = new RichText( dialogDescriptionString, {
      lineWrap: 500,
      boundsMethod: 'accurate',

      tagName: 'p',
      innerContent: dialogDescriptionString
    } );

    const speakDescriptionParagraph = () => {
      const utterance = new SelfVoicingUtterance( {
        alert: dialogDescriptionString,
        cancelOther: false
      } );
      phet.joist.sim.selfVoicingUtteranceQueue.addToBack( utterance );
    };

    // a wrapper that surrounds the descriptionParagraph with a Node that is focusable
    // and has input listeners that make it possible to click the text to hear information
    // about it
    const selfVoicingWrapper = new SelfVoicingWrapperNode( descriptionParagraph, {
      listenerOptions: {
        onFocusIn: speakDescriptionParagraph,
        onPress: speakDescriptionParagraph
      }
    } );
    selfVoicingWrapper.addChild( descriptionParagraph );

    const exampleButton = new TextPushButton( testButtonContent, {
      font: contentFont,
      listener: () => {
        phet.joist.sim.selfVoicingUtteranceQueue.addToBack( pressedString );
      }
    } );

    const exampleSliderProperty = new NumberProperty( 5 );
    const exampleSliderRange = new Range( 0, 10 );
    const exampleSlider = new HSlider( exampleSliderProperty, new Range( 0, 10 ) );

    const exampleComponents = new HBox( {
      children: [ exampleButton, exampleSlider ]
    } );

    // continues to the simulation, closing this dialog
    const continueButton = new TextPushButton( continueButtonContent, {
      font: contentFont,
      listener: () => {
        this.hide();
      }
    } );

    const content = new VBox( {
      children: [ selfVoicingWrapper, exampleComponents, continueButton ],
      spacing: 50
    } );

    super( content, options );

    // @private {Node}
    this.selfVoicingWrapper = selfVoicingWrapper;

    // listeners that provide actual self-voicing content on the example components
    exampleButton.addInputListener( new SelfVoicingInputListener( {
      onFocusIn: () => {
        phet.joist.sim.selfVoicingUtteranceQueue.addToBack( testButtonContent );
      }
    } ) );

    exampleSlider.addInputListener( new SelfVoicingInputListener( {
      onFocusIn: () => {
        phet.joist.sim.selfVoicingUtteranceQueue.addToBack( sliderLabelString );
      }
    } ) );

    continueButton.addInputListener(  new SelfVoicingInputListener( {
      onFocusIn: () => {
        phet.joist.sim.selfVoicingUtteranceQueue.addToBack( continueButtonContent );
      }
    } ) );

    exampleSlider.addInputListener( {
      click: () => {
        const hintString = StringUtils.fillIn( grabDragHintPatternString, {
          manipulation: 'Move me'
        } );

        phet.joist.sim.selfVoicingUtteranceQueue.addToBack( hintString );
      }
    } );

    // this is a workaround to speak the label of the close button when this dialog is
    // open - when this feature gets built out more thoroughly this would just be
    // attached to focus of the Dialog's private close button - but I really didn't want
    // to add code to Dialog, and I didn't want to work in a branch so this is here for now
    Display.focusProperty.lazyLink( focus => {
      if ( focus && focus.trail.lastNode().innerContent === 'Close' ) {
        phet.joist.sim.selfVoicingUtteranceQueue.addToBack( 'Close' );
      }
    } );

    const valueChangeUtterance = new SelfVoicingUtterance();
    exampleSliderProperty.lazyLink( value => {
      valueChangeUtterance.alert = value + '';
      phet.joist.sim.selfVoicingUtteranceQueue.addToBack( valueChangeUtterance );
    } );

    let positionOnValueChange = null;
    exampleSlider.swipeStart = event => {
      phet.joist.sim.selfVoicingUtteranceQueue.addToBack( grabbedAlertString );
      positionOnValueChange = event.pointer.point;
    };
    exampleSlider.swipeEnd = () => {
      const releasedAlert = new SelfVoicingUtterance( {
        cancelOther: false,
        alert: releasedString
      } );
      phet.joist.sim.selfVoicingUtteranceQueue.addToBack( releasedAlert );
    };
    exampleSlider.swipeMove = event => {
      const nextSwipePosition = event.pointer.point;
      const swipeDelta = nextSwipePosition.minus( positionOnValueChange );
      const distance = nextSwipePosition.distance( positionOnValueChange );

      if ( distance > 30 ) {
        const swipeAngle = swipeDelta.angle;

        const swipeRight = Utils.equalsEpsilon( Math.abs( swipeAngle ), 0, Math.PI / 4 );
        const swipeLeft = Utils.equalsEpsilon( Math.abs( swipeAngle ), Math.PI, Math.PI / 4 );
        const swipeUp = Utils.equalsEpsilon( swipeAngle, -Math.PI / 2, Math.PI / 4 );
        const swipeDown = Utils.equalsEpsilon( swipeAngle, Math.PI / 2, Math.PI / 4 );

        let nextValue = exampleSliderProperty.value;
        if ( swipeRight || swipeUp ) {
          nextValue++;
        }
        else if ( swipeLeft || swipeDown ) {
          nextValue--;
        }

        if ( exampleSliderRange.contains( nextValue ) ) {
          exampleSliderProperty.set( nextValue );
        }

        positionOnValueChange = nextSwipePosition;
      }
    };
  }

  /**
   * Puts focus on the intro paragraph, useful to do when the dialog first opens.
   * @public
   */
  focusIntroDescription() {
    this.selfVoicingWrapper.focus();
  }
}

sceneryPhet.register( 'SelfVoicingIntroDialog', SelfVoicingIntroDialog );
export default SelfVoicingIntroDialog;<commit_msg>move string up to constants<commit_after>// Copyright 2020, University of Colorado Boulder

/**
 * A dialog with a few basic UI components with self-voicing and gesture control to give
 * the user a chance to play with components before interacting with them in the simulation.
 * This is intended for use only in prototypes for user interviews. The hope is that providing
 * user with a chance to play with components before jumping into the sim will separate
 * learning of the controls versus learning of the content.
 *
 * @author Jesse Greenberg
 */

import NumberProperty from '../../../../axon/js/NumberProperty.js';
import Range from '../../../../dot/js/Range.js';
import Utils from '../../../../dot/js/Utils.js';
import StringUtils from '../../../../phetcommon/js/util/StringUtils.js';
import merge from '../../../../phet-core/js/merge.js';
import Display from '../../../../scenery/js/display/Display.js';
import HBox from '../../../../scenery/js/nodes/HBox.js';
import RichText from '../../../../scenery/js/nodes/RichText.js';
import Text from '../../../../scenery/js/nodes/Text.js';
import VBox from '../../../../scenery/js/nodes/VBox.js';
import TextPushButton from '../../../../sun/js/buttons/TextPushButton.js';
import Dialog from '../../../../sun/js/Dialog.js';
import HSlider from '../../../../sun/js/HSlider.js';
import SelfVoicingUtterance from '../../../../utterance-queue/js/SelfVoicingUtterance.js';
import PhetFont from '../../PhetFont.js';
import sceneryPhet from '../../sceneryPhet.js';
import sceneryPhetStrings from '../../sceneryPhetStrings.js';
import SelfVoicingInputListener from './SelfVoicingInputListener.js';
import SelfVoicingWrapperNode from './SelfVoicingWrapperNode.js';

// constants
const contentFont = new PhetFont( 16 );
const titleFont = new PhetFont( 32 );

const titleString = 'Gestures';
const dialogDescriptionString = 'Welcome! This simulation uses web speech and some custom gestures, here are a couple ' +
                                'to test out to get things going. When ready, continue to simulation.';
const continueButtonContent = 'Continue to simulation';
const testButtonContent = 'Activate Me!';
const pressedString = 'Thanks for activating me! You rock!';
const sliderLabelString = 'Move Me';
const sliderInteractionHintString = 'move me';
const grabDragHintPatternString = sceneryPhetStrings.a11y.selfVoicing.grabDragHintPattern;
const grabbedAlertString = sceneryPhetStrings.a11y.selfVoicing.grabbedAlert;
const releasedString = sceneryPhetStrings.a11y.grabDrag.released;

class SelfVoicingIntroDialog extends Dialog {
  constructor( options ) {
    if ( options ) {
      assert && assert( options.title === undefined, 'SelfVoicingIntroDialog sets title' );
    }

    options = merge( {
      title: new Text( titleString, {
        font: titleFont,
        tagName: 'h1',
        innerContent: titleString
      } )
    }, options );

    const descriptionParagraph = new RichText( dialogDescriptionString, {
      lineWrap: 500,
      boundsMethod: 'accurate',

      tagName: 'p',
      innerContent: dialogDescriptionString
    } );

    const speakDescriptionParagraph = () => {
      const utterance = new SelfVoicingUtterance( {
        alert: dialogDescriptionString,
        cancelOther: false
      } );
      phet.joist.sim.selfVoicingUtteranceQueue.addToBack( utterance );
    };

    // a wrapper that surrounds the descriptionParagraph with a Node that is focusable
    // and has input listeners that make it possible to click the text to hear information
    // about it
    const selfVoicingWrapper = new SelfVoicingWrapperNode( descriptionParagraph, {
      listenerOptions: {
        onFocusIn: speakDescriptionParagraph,
        onPress: speakDescriptionParagraph
      }
    } );
    selfVoicingWrapper.addChild( descriptionParagraph );

    const exampleButton = new TextPushButton( testButtonContent, {
      font: contentFont,
      listener: () => {
        phet.joist.sim.selfVoicingUtteranceQueue.addToBack( pressedString );
      }
    } );

    const exampleSliderProperty = new NumberProperty( 5 );
    const exampleSliderRange = new Range( 0, 10 );
    const exampleSlider = new HSlider( exampleSliderProperty, new Range( 0, 10 ) );

    const exampleComponents = new HBox( {
      children: [ exampleButton, exampleSlider ]
    } );

    // continues to the simulation, closing this dialog
    const continueButton = new TextPushButton( continueButtonContent, {
      font: contentFont,
      listener: () => {
        this.hide();
      }
    } );

    const content = new VBox( {
      children: [ selfVoicingWrapper, exampleComponents, continueButton ],
      spacing: 50
    } );

    super( content, options );

    // @private {Node}
    this.selfVoicingWrapper = selfVoicingWrapper;

    // listeners that provide actual self-voicing content on the example components
    exampleButton.addInputListener( new SelfVoicingInputListener( {
      onFocusIn: () => {
        phet.joist.sim.selfVoicingUtteranceQueue.addToBack( testButtonContent );
      }
    } ) );

    exampleSlider.addInputListener( new SelfVoicingInputListener( {
      onFocusIn: () => {
        phet.joist.sim.selfVoicingUtteranceQueue.addToBack( sliderLabelString );
      }
    } ) );

    continueButton.addInputListener(  new SelfVoicingInputListener( {
      onFocusIn: () => {
        phet.joist.sim.selfVoicingUtteranceQueue.addToBack( continueButtonContent );
      }
    } ) );

    exampleSlider.addInputListener( {
      click: () => {
        const hintString = StringUtils.fillIn( grabDragHintPatternString, {
          manipulation: sliderInteractionHintString
        } );

        phet.joist.sim.selfVoicingUtteranceQueue.addToBack( hintString );
      }
    } );

    // this is a workaround to speak the label of the close button when this dialog is
    // open - when this feature gets built out more thoroughly this would just be
    // attached to focus of the Dialog's private close button - but I really didn't want
    // to add code to Dialog, and I didn't want to work in a branch so this is here for now
    Display.focusProperty.lazyLink( focus => {
      if ( focus && focus.trail.lastNode().innerContent === 'Close' ) {
        phet.joist.sim.selfVoicingUtteranceQueue.addToBack( 'Close' );
      }
    } );

    const valueChangeUtterance = new SelfVoicingUtterance();
    exampleSliderProperty.lazyLink( value => {
      valueChangeUtterance.alert = value + '';
      phet.joist.sim.selfVoicingUtteranceQueue.addToBack( valueChangeUtterance );
    } );

    let positionOnValueChange = null;
    exampleSlider.swipeStart = event => {
      phet.joist.sim.selfVoicingUtteranceQueue.addToBack( grabbedAlertString );
      positionOnValueChange = event.pointer.point;
    };
    exampleSlider.swipeEnd = () => {
      const releasedAlert = new SelfVoicingUtterance( {
        cancelOther: false,
        alert: releasedString
      } );
      phet.joist.sim.selfVoicingUtteranceQueue.addToBack( releasedAlert );
    };
    exampleSlider.swipeMove = event => {
      const nextSwipePosition = event.pointer.point;
      const swipeDelta = nextSwipePosition.minus( positionOnValueChange );
      const distance = nextSwipePosition.distance( positionOnValueChange );

      if ( distance > 30 ) {
        const swipeAngle = swipeDelta.angle;

        const swipeRight = Utils.equalsEpsilon( Math.abs( swipeAngle ), 0, Math.PI / 4 );
        const swipeLeft = Utils.equalsEpsilon( Math.abs( swipeAngle ), Math.PI, Math.PI / 4 );
        const swipeUp = Utils.equalsEpsilon( swipeAngle, -Math.PI / 2, Math.PI / 4 );
        const swipeDown = Utils.equalsEpsilon( swipeAngle, Math.PI / 2, Math.PI / 4 );

        let nextValue = exampleSliderProperty.value;
        if ( swipeRight || swipeUp ) {
          nextValue++;
        }
        else if ( swipeLeft || swipeDown ) {
          nextValue--;
        }

        if ( exampleSliderRange.contains( nextValue ) ) {
          exampleSliderProperty.set( nextValue );
        }

        positionOnValueChange = nextSwipePosition;
      }
    };
  }

  /**
   * Puts focus on the intro paragraph, useful to do when the dialog first opens.
   * @public
   */
  focusIntroDescription() {
    this.selfVoicingWrapper.focus();
  }
}

sceneryPhet.register( 'SelfVoicingIntroDialog', SelfVoicingIntroDialog );
export default SelfVoicingIntroDialog;<|endoftext|>


<commit_before>#!/usr/bin/env node
'use strict'; /*jslint node: true, es5: true, indent: 2 */
var async = require('async');
var fs = require('fs');
var os = require('os');
var stream = require('stream');
var util = require('util');

var inference = require('./inference');
var merge = require('./merge');
var Parser = exports.Parser = require('./parser');
var Stringifier = exports.Stringifier = require('./stringifier');

var JSONStringifier = function(opts) {
  stream.Transform.call(this, {objectMode: true});
  this._readableState.objectMode = false;
};
util.inherits(JSONStringifier, stream.Transform);
JSONStringifier.prototype._transform = function(chunk, encoding, callback) {
  this.push(JSON.stringify(chunk) + os.EOL, encoding);
  callback();
};

var ObjectFilter = function(fields) {
  // objects in, objects out
  stream.Transform.call(this, {objectMode: true});
  this.fields = {};
  for (var i = 0, l = fields.length; i < l; i++) {
    this.fields[fields[i]] = 1;
  }
};
util.inherits(ObjectFilter, stream.Transform);
ObjectFilter.prototype._transform = function(chunk, encoding, callback) {
  var filtered = {};
  for (var field in this.fields)
    filtered[field] = chunk[field];
  this.push(filtered, encoding);
  callback();
};

var ObjectOmitter = function(fields) {
  // objects in, objects out
  stream.Transform.call(this, {objectMode: true});
  this.fields = {};
  for (var i = 0, l = fields.length; i < l; i++) {
    this.fields[fields[i]] = 1;
  }
};
util.inherits(ObjectOmitter, stream.Transform);
ObjectOmitter.prototype._transform = function(chunk, encoding, callback) {
  for (var field in this.fields)
    delete chunk[field];
  this.push(chunk, encoding);
  callback();
};

function pluck(xs, prop) {
  return xs.map(function(x) { return x[prop]; });
}

var whitespace_literals = {
  '\r': '\\r',
  '\n': '\\n',
  '\t': '\\t',
};
function escapeWhitespace(s) {
  return whitespace_literals[s];
}

function describe(stream, filename, opts, callback) {
  if (filename) {
    console.log(filename);
  }

  var rows = [];

  var parser = stream.pipe(new Parser());
  var onData = function(row) {
    rows.push(row);
    if (rows.length > 10) {
      parser.removeListener('data', onData);
      // parser.columns are ordered like the original, inference.columns may not be
      var columns = parser.columns || inference.columns(rows);
      for (var i = 0, l = parser.columns.length; i < l; i++) {
        var name = parser.columns[i];
        console.log('[' + i + '] ' + name + ':');

        var cells = pluck(rows, name).join(', ').replace(/\r|\n|\t/g, escapeWhitespace);

        var segment = opts.width - 2;
        for (var start = 0, end = cells.length; start < end; start += segment) {
          console.log('  ' + cells.slice(start, start + segment));
        }
      }
      callback();
    }
  };
  parser.on('data', onData);
}

function read(stream, filename, opts, callback) {
  if (filename) {
    console.error('Reading ' + filename);
  }

  stream = stream.pipe(new Parser());

  if (opts.omit) {
    stream = stream.pipe(new ObjectOmitter(opts.omit.split(/,/g)));
  }

  if (opts.filter) {
    stream = stream.pipe(new ObjectFilter(opts.filter.split(/,/g)));
  }

  var stringifier = opts.json ? new JSONStringifier() : new Stringifier(opts);
  stream = stream.pipe(stringifier);
  stream = stream.pipe(process.stdout);

  stream.on('finish', callback);
  stream.on('error', callback.bind(null));
}

if (require.main === module) {
  var optimist = require('optimist')
    .usage([
      'Consolidate any tabular format.',
      '',
      'Usage: <sprints.txt sv [options] > sprints.csv',
      '   or: sv [options] ~/Desktop/**/*.csv > ~/all.csv',
      '',
      'Options:',
      '  -p, --peek 10       infer columns from first ten lines of input',
      '  -d, --delimiter ,   field separator',
      '  -q, --quotechar "   mark beginning and end of fields containing delimiter',
      '  -e, --escapechar \\  escape quotechars when quoted',
      '  -j, --json          write one JSON object per row',
      '      --filter a,b    keep only fields a and b in the results',
      '      --omit c,d      leave out fields x and y from the results',
      '                      do not use filter and omit together',
      '      --describe      only describe the data, using headers and a few examples',
      '      --width         width of the terminal (used by --describe)',
      '      --merge         merge multiple files supplied as command line args',
      '  -v  --verbose       turn up the verbosity (still all on STDERR)',
      '',
      'STDIN, if supplied, will be coerced to utf8',
    ].join('\n'))
    .string(['delimiter', 'quotechar', 'escapechar'])
    .boolean(['json', 'describe', 'merge', 'verbose'])
    .alias({
      p: 'peek',
      d: 'delimiter',
      q: 'quotechar',
      e: 'escapechar',
      j: 'json',
      v: 'verbose',
    })
    .default({
      width: process.stdout.columns || 80,
    });
  var argv = optimist.argv;

  var func = argv.describe ? describe : read; // function (stream, filename, opts, callback) { ... }

  if (argv.help) {
    optimist.showHelp();
    console.log('ARGV: ' + process.argv.join(' '));
  }
  else if (!process.stdin.isTTY) {
    // process.stdin.setEncoding('utf8');
    func(process.stdin, null, argv, function(err) {
      if (err) throw err;
      console.error('Done.');
    });
  }
  else if (argv._.length) {
    if (argv.merge) {
      console.error('Merging.');
      merge(argv._, argv, function(err) {
        if (err) throw err;
        console.error('Done.');
      });
    }
    else {
      async.eachSeries(argv._, function(filepath, callback) {
        var stream = fs.createReadStream(filepath);
        func(stream, filepath, argv, callback);
        console.error(''); // newline
      }, function(err) {
        if (err) throw err;
        console.error('Done.');
      });
    }
  }
  else {
    optimist.showHelp();
    console.error('You must supply data via STDIN or as unflagged command line arguments.');
  }
}
<commit_msg>Fold callbacks into one: exit()<commit_after>#!/usr/bin/env node
'use strict'; /*jslint node: true, es5: true, indent: 2 */
var async = require('async');
var fs = require('fs');
var os = require('os');
var stream = require('stream');
var util = require('util');

var inference = require('./inference');
var merge = require('./merge');
var Parser = exports.Parser = require('./parser');
var Stringifier = exports.Stringifier = require('./stringifier');

var JSONStringifier = function(opts) {
  stream.Transform.call(this, {objectMode: true});
  this._readableState.objectMode = false;
};
util.inherits(JSONStringifier, stream.Transform);
JSONStringifier.prototype._transform = function(chunk, encoding, callback) {
  this.push(JSON.stringify(chunk) + os.EOL, encoding);
  callback();
};

var ObjectFilter = function(fields) {
  // objects in, objects out
  stream.Transform.call(this, {objectMode: true});
  this.fields = {};
  for (var i = 0, l = fields.length; i < l; i++) {
    this.fields[fields[i]] = 1;
  }
};
util.inherits(ObjectFilter, stream.Transform);
ObjectFilter.prototype._transform = function(chunk, encoding, callback) {
  var filtered = {};
  for (var field in this.fields)
    filtered[field] = chunk[field];
  this.push(filtered, encoding);
  callback();
};

var ObjectOmitter = function(fields) {
  // objects in, objects out
  stream.Transform.call(this, {objectMode: true});
  this.fields = {};
  for (var i = 0, l = fields.length; i < l; i++) {
    this.fields[fields[i]] = 1;
  }
};
util.inherits(ObjectOmitter, stream.Transform);
ObjectOmitter.prototype._transform = function(chunk, encoding, callback) {
  for (var field in this.fields)
    delete chunk[field];
  this.push(chunk, encoding);
  callback();
};

function pluck(xs, prop) {
  return xs.map(function(x) { return x[prop]; });
}

var whitespace_literals = {
  '\r': '\\r',
  '\n': '\\n',
  '\t': '\\t',
};
function escapeWhitespace(s) {
  return whitespace_literals[s];
}

function describe(stream, filename, opts, callback) {
  if (filename) {
    console.log(filename);
  }

  var rows = [];

  var parser = stream.pipe(new Parser());
  var onData = function(row) {
    rows.push(row);
    if (rows.length > 10) {
      parser.removeListener('data', onData);
      // parser.columns are ordered like the original, inference.columns may not be
      var columns = parser.columns || inference.columns(rows);
      for (var i = 0, l = parser.columns.length; i < l; i++) {
        var name = parser.columns[i];
        console.log('[' + i + '] ' + name + ':');

        var cells = pluck(rows, name).join(', ').replace(/\r|\n|\t/g, escapeWhitespace);

        var segment = opts.width - 2;
        for (var start = 0, end = cells.length; start < end; start += segment) {
          console.log('  ' + cells.slice(start, start + segment));
        }
      }
      callback();
    }
  };
  parser.on('data', onData);
}

function read(stream, filename, opts, callback) {
  if (filename) {
    console.error('Reading ' + filename);
  }

  stream = stream.pipe(new Parser());

  if (opts.omit) {
    stream = stream.pipe(new ObjectOmitter(opts.omit.split(/,/g)));
  }

  if (opts.filter) {
    stream = stream.pipe(new ObjectFilter(opts.filter.split(/,/g)));
  }

  var stringifier = opts.json ? new JSONStringifier() : new Stringifier(opts);
  stream = stream.pipe(stringifier);
  stream = stream.pipe(process.stdout);

  stream.on('finish', callback);
  stream.on('error', callback.bind(null));
}

if (require.main === module) {
  var optimist = require('optimist')
    .usage([
      'Consolidate any tabular format.',
      '',
      'Usage: <sprints.txt sv [options] > sprints.csv',
      '   or: sv [options] ~/Desktop/**/*.csv > ~/all.csv',
      '',
      'Options:',
      '  -p, --peek 10       infer columns from first ten lines of input',
      '  -d, --delimiter ,   field separator',
      '  -q, --quotechar "   mark beginning and end of fields containing delimiter',
      '  -e, --escapechar \\  escape quotechars when quoted',
      '  -j, --json          write one JSON object per row',
      '      --filter a,b    keep only fields a and b in the results',
      '      --omit c,d      leave out fields x and y from the results',
      '                      do not use filter and omit together',
      '      --describe      only describe the data, using headers and a few examples',
      '      --width         width of the terminal (used by --describe)',
      '      --merge         merge multiple files supplied as command line args',
      '  -v  --verbose       turn up the verbosity (still all on STDERR)',
      '',
      'STDIN, if supplied, will be coerced to utf8',
    ].join('\n'))
    .string(['delimiter', 'quotechar', 'escapechar'])
    .boolean(['json', 'describe', 'merge', 'verbose'])
    .alias({
      p: 'peek',
      d: 'delimiter',
      q: 'quotechar',
      e: 'escapechar',
      j: 'json',
      v: 'verbose',
    })
    .default({
      width: process.stdout.columns || 80,
    });
  var argv = optimist.argv;

  var func = argv.describe ? describe : read; // function (stream, filename, opts, callback) { ... }
  var exit = function(err) {
    if (err) throw err;
    if (argv.verbose) {
      console.error('Done.');
    }
    process.exit();
  };

  if (argv.help) {
    optimist.showHelp();
    console.log('ARGV: ' + process.argv.join(' '));
  }
  else if (!process.stdin.isTTY) {
    // process.stdin.setEncoding('utf8');
    func(process.stdin, null, argv, exit);
  }
  else if (argv._.length) {
    if (argv.merge) {
      console.error('Merging.');
      merge(argv._, argv, exit);
    }
    else {
      async.eachSeries(argv._, function(filepath, callback) {
        var stream = fs.createReadStream(filepath);
        func(stream, filepath, argv, callback);
        console.error(''); // newline
      }, exit);
    }
  }
  else {
    optimist.showHelp();
    console.error('You must supply data via STDIN or as unflagged command line arguments.');
  }
}
<|endoftext|>