<commit_before>/* global Authorship, Affiliation, Institute*/
"use strict";

const _ = require('lodash');
const BaseModel = require("../lib/BaseModel.js");

const fields = [
    'researchEntity',
    'document',
    'affiliations',
    'position',
    'synchronize',
    'corresponding',
    'public',
    'favorite',
    'first_coauthor',
    'last_coauthor',
    'oral_presentation'
];

module.exports = _.merge({}, BaseModel, {

    attributes: {
        researchEntity: {
            model: 'User',
        },
        document: {
            model: 'Document'
        },
        affiliations: {
            collection: 'institute',
            via: 'authorships',
            through: 'affiliation'
        },
        position: 'integer',
        public: 'boolean',
        favorite: 'boolean',
        synchronize: 'boolean',
        corresponding: 'boolean',
        first_coauthor: 'boolean',
        last_coauthor: 'boolean',
        oral_presentation: 'boolean',
        unverify: function () {
            this.researchEntity = null;
            this.synchronize = null;
            return this.savePromise();
        }
    },
    beforeDestroy: async function (criteria, cb) {
        if (!criteria || !criteria.where) {
            cb();
            return;
        }

        const authorships = await Authorship.find(criteria);
        for (let a of authorships)
            await Affiliation.destroy({authorship: a.id});

        cb();
    },
    getEmpty: function () {
        return {
            synchronize: null,
            researchEntity: null,
            corresponding: false,
            'public': false,
            favorite: false,
            first_coauthor: false,
            last_coauthor: false,
            oral_presentation: false,
            affiliations: []
        };
    },
    getFields: function () {
        return fields;
    },
    filterFields: function (authorshipData) {
        if (!_.isObject(authorshipData))
            return {};
        const newAuthorship = {};
        fields.forEach(f => newAuthorship[f] = authorshipData[f]);
        return newAuthorship;
    },
    createEmptyAuthorships: async function (doc, authorshipsData) {
        //TODO Add empty authorships generated from authorStr
        if (!_.isArray(authorshipsData))
            return;

        const filteredAuthorshipsData = _.map(authorshipsData, a => _.pick(a, fields));
        filteredAuthorshipsData.forEach(a => a.document = doc.id);
        const authorships = await Authorship.create(filteredAuthorshipsData);

        const affiliations = [];
        for (const authData of authorshipsData)
            if (_.isArray(authData.affiliations))
                for (const aff of authData.affiliations) {
                    const institutes = await Authorship.getFixedCollection(Institute, aff);
                    const auth = authorships.find(a => a.position === authData.position);
                    affiliations.push({
                        document: doc.id,
                        authorship: auth.id,
                        institute: institutes
                    });
                }
        if (affiliations.length)
            await Affiliation.create(affiliations);
    },
    updateAuthorships: async function (doc, authorshipsData) {
        //TODO update authorships instead of delete/insert empty
        //TODO Fix authorship bug on authorStr changes
        await Authorship.destroy({document: doc.id});
        await Authorship.createEmptyAuthorships(doc, authorshipsData);
    },
    clone: function (authorship) {
        const newAuthorship = Authorship.filterFields(authorship);
        newAuthorship.affiliations = authorship.affiliations.map(aff => {
            if (aff.institute)
                return aff.institute;

            return aff.id;
        });

        return newAuthorship;
    },
    updateAuthorshipData: async function (authorshipId, docId, authorshipData) {
        if (!docId) throw "updateAuthorshipData error!";

        const newAuthData = Authorship.clone(authorshipData);
        newAuthData.document = docId;

        await Affiliation.destroy({authorship: authorshipId, document: docId});
        await Authorship.update({id: authorshipId}, newAuthData);
    },
    createAuthorshipData: async function (docId, authorshipData) {
        if (!docId) throw "updateAuthorshipData error!";

        const newAuthData = Authorship.clone(authorshipData);
        newAuthData.document = docId;

        await Authorship.create(newAuthData);
    },
    setPrivacy: async function (documentId, userId, privacy) {
        const authorship = await Authorship.findOne({document: documentId, researchEntity: userId});
        if (!authorship)
            throw 'Athorship not found';

        authorship.public = !!privacy;
        return authorship.savePromise();
    },
    setFavorite: async function (documentId, userId, favorite) {
        if (favorite) {
            const favorited = await Authorship.find({researchEntity: userId, favorite: true});
            if (favorited.length >= sails.config.scientilla.maxUserFavorite)
                throw 'Favorite max limit reached';
        }

        const authorship = await Authorship.findOne({document: documentId, researchEntity: userId});
        if (!authorship)
            throw 'Athorship not found';

        authorship.favorite = !!favorite;
        return authorship.savePromise();
    }
});

<commit_msg>Fixed bug on draft creation<commit_after>/* global Authorship, Affiliation, Institute*/
"use strict";

const _ = require('lodash');
const BaseModel = require("../lib/BaseModel.js");

const fields = [
    'researchEntity',
    'document',
    'affiliations',
    'position',
    'synchronize',
    'corresponding',
    'public',
    'favorite',
    'first_coauthor',
    'last_coauthor',
    'oral_presentation'
];

module.exports = _.merge({}, BaseModel, {

    attributes: {
        researchEntity: {
            model: 'User',
        },
        document: {
            model: 'Document'
        },
        affiliations: {
            collection: 'institute',
            via: 'authorships',
            through: 'affiliation'
        },
        position: 'integer',
        public: 'boolean',
        favorite: 'boolean',
        synchronize: 'boolean',
        corresponding: 'boolean',
        first_coauthor: 'boolean',
        last_coauthor: 'boolean',
        oral_presentation: 'boolean',
        unverify: function () {
            this.researchEntity = null;
            this.synchronize = null;
            return this.savePromise();
        }
    },
    beforeDestroy: async function (criteria, cb) {
        if (!criteria || !criteria.where) {
            cb();
            return;
        }

        const authorships = await Authorship.find(criteria);
        for (let a of authorships)
            await Affiliation.destroy({authorship: a.id});

        cb();
    },
    getEmpty: function () {
        return {
            synchronize: null,
            researchEntity: null,
            corresponding: false,
            'public': false,
            favorite: false,
            first_coauthor: false,
            last_coauthor: false,
            oral_presentation: false,
            affiliations: []
        };
    },
    getFields: function () {
        return fields;
    },
    filterFields: function (authorshipData) {
        if (!_.isObject(authorshipData))
            return {};
        const newAuthorship = {};
        fields.forEach(f => newAuthorship[f] = authorshipData[f]);
        return newAuthorship;
    },
    createEmptyAuthorships: async function (doc, authorshipsData) {
        //TODO Add empty authorships generated from authorStr
        if (!_.isArray(authorshipsData))
            return;

        const filteredAuthorshipsData = authorshipsData.map(a => Authorship.filterFields(a));
        filteredAuthorshipsData.forEach(a => {
            a.document = doc.id;
            delete a.affiliations
        });
        const authorships = await Authorship.create(filteredAuthorshipsData);

        const affiliations = [];
        for (const authData of authorshipsData)
            if (_.isArray(authData.affiliations))
                for (const aff of authData.affiliations) {
                    const institutes = await Authorship.getFixedCollection(Institute, aff);
                    const auth = authorships.find(a => a.position === authData.position);
                    affiliations.push({
                        document: doc.id,
                        authorship: auth.id,
                        institute: institutes
                    });
                }
        if (affiliations.length)
            await Affiliation.create(affiliations);
    },
    updateAuthorships: async function (doc, authorshipsData) {
        //TODO update authorships instead of delete/insert empty
        //TODO Fix authorship bug on authorStr changes
        await Authorship.destroy({document: doc.id});
        await Authorship.createEmptyAuthorships(doc, authorshipsData);
    },
    clone: function (authorship) {
        const newAuthorship = Authorship.filterFields(authorship);
        newAuthorship.affiliations = authorship.affiliations.map(aff => {
            if (aff.institute)
                return aff.institute;

            return aff.id;
        });

        return newAuthorship;
    },
    updateAuthorshipData: async function (authorshipId, docId, authorshipData) {
        if (!docId) throw "updateAuthorshipData error!";

        const newAuthData = Authorship.clone(authorshipData);
        newAuthData.document = docId;

        await Affiliation.destroy({authorship: authorshipId, document: docId});
        await Authorship.update({id: authorshipId}, newAuthData);
    },
    createAuthorshipData: async function (docId, authorshipData) {
        if (!docId) throw "updateAuthorshipData error!";

        const newAuthData = Authorship.clone(authorshipData);
        newAuthData.document = docId;

        await Authorship.create(newAuthData);
    },
    setPrivacy: async function (documentId, userId, privacy) {
        const authorship = await Authorship.findOne({document: documentId, researchEntity: userId});
        if (!authorship)
            throw 'Athorship not found';

        authorship.public = !!privacy;
        return authorship.savePromise();
    },
    setFavorite: async function (documentId, userId, favorite) {
        if (favorite) {
            const favorited = await Authorship.find({researchEntity: userId, favorite: true});
            if (favorited.length >= sails.config.scientilla.maxUserFavorite)
                throw 'Favorite max limit reached';
        }

        const authorship = await Authorship.findOne({document: documentId, researchEntity: userId});
        if (!authorship)
            throw 'Athorship not found';

        authorship.favorite = !!favorite;
        return authorship.savePromise();
    }
});

<|endoftext|>


<commit_before>var passport = require('passport');

/**
 * Passport Middleware
 *
 * Policy for Sails that initializes Passport.js and as well as its built-in
 * session support.
 *
 * In a typical web application, the credentials used to authenticate a user
 * will only be transmitted during the login request. If authentication
 * succeeds, a session will be established and maintained via a cookie set in
 * the user's browser.
 *
 * Each subsequent request will not contain credentials, but rather the unique
 * cookie that identifies the session. In order to support login sessions,
 * Passport will serialize and deserialize user instances to and from the
 * session.
 *
 * For more information on the Passport.js middleware, check out:
 * http://passportjs.org/guide/configure/
 *
 * @param {Object}   req
 * @param {Object}   res
 * @param {Function} next
 */
var http = require('http');
var methods = ['login', 'logIn', 'logout', 'logOut', 'isAuthenticated', 'isUnauthenticated'];

module.exports = function (req, res, next) {
  var passport = sails.services.passport;

  // Initialize Passport
  passport.initialize()(req, res, function () {
    // Use the built-in sessions
    passport.session()(req, res, function () {

      // Make the request's passport methods available for socket
      if (req.isSocket) {
        _.each(methods, function (method) {
          req[method] = http.IncomingMessage.prototype[method].bind(req);
        });
      }

      // Make the user available throughout the frontend (for views)
      res.locals.user = req.user;

      next();
    });
  });
};
<commit_msg>#107 sails.services.passport throwing issue <commit_after>var passport = require('passport');

/**
 * Passport Middleware
 *
 * Policy for Sails that initializes Passport.js and as well as its built-in
 * session support.
 *
 * In a typical web application, the credentials used to authenticate a user
 * will only be transmitted during the login request. If authentication
 * succeeds, a session will be established and maintained via a cookie set in
 * the user's browser.
 *
 * Each subsequent request will not contain credentials, but rather the unique
 * cookie that identifies the session. In order to support login sessions,
 * Passport will serialize and deserialize user instances to and from the
 * session.
 *
 * For more information on the Passport.js middleware, check out:
 * http://passportjs.org/guide/configure/
 *
 * @param {Object}   req
 * @param {Object}   res
 * @param {Function} next
 */
var http = require('http');
var methods = ['login', 'logIn', 'logout', 'logOut', 'isAuthenticated', 'isUnauthenticated'];

module.exports = function (req, res, next) {
  var passport = require('passport');

  // Initialize Passport
  passport.initialize()(req, res, function () {
    // Use the built-in sessions
    passport.session()(req, res, function () {

      // Make the request's passport methods available for socket
      if (req.isSocket) {
        _.each(methods, function (method) {
          req[method] = http.IncomingMessage.prototype[method].bind(req);
        });
      }

      // Make the user available throughout the frontend (for views)
      res.locals.user = req.user;

      next();
    });
  });
};
<|endoftext|>


<commit_before>var express = require('express');
var http = require('http');
var bodyParser = require('body-parser');
var async = require('async');

var logger = require(__libs+'/eris/eris-logger');
var taskManager = require(__js+'/taskManager');

var init = function () {

    var log = logger.getLogger('eris.chain.server');

    var portHTTP = process.env.IDI_PORT || __settings.eris.server.port_http || 8082;
    var app = express();

    // Configure PORTAL
    // app.use('/'+(__settings.eris.server.contextPath || 'hello-eris'), express.static(__dirname + '/ui'));

    // Configure JSON parsing as default
    app.use(bodyParser.json());

    /**
     * _handleErr - description
     *
     * @param  {type} err description
     * @param  {type} res description
     * @return {type}     description
     */
    function _handleErr (err, res) {
        if (err) {
            log.error(err.stack);
            res.sendStatus(500);
        }
    }

    /**
     * ROUTING
     */

    // GET all available task objects
    app.get('/tasks', function (req, res) {
        async.waterfall([
            // Get all available task contract addresses
            taskManager.getAllTaskAddresses,
            // Map each address to its task contract and callback an array of task objects
            function (addresses, callback) {
                async.map(addresses, taskManager.getTaskAtAddress, function (err, tasks) {
                    callback(err, tasks);
                });
            }
        ], function (err, tasks) {
            _handleErr(err, res);
            log.info("GET /tasks: ", tasks);
            res.json({data: tasks});
        });
    });

    // POST new task
    app.post('/tasks', function (req, res) {
        var task = req.body;

        log.debug("POST task: ", task);
        taskManager.addTask(task, function (err, isOverwrite) {
            _handleErr(err, res);
            res.send(isOverwrite);
        });
    });

    // TODO not DRY, abstract this further
    app.get('/new-id/:target', function (req, res) {
        var newId;

        log.debug("GET /new-id/" + req.params.target);
        switch (req.params.target) {
            case 'task':
                return taskManager.getTaskListSize(function (err, size) {
                    _handleErr(err, res);
                    // increment size by one to mint a new id number & turn it back into string type
                    newId = String(Number(size) + 1);
                    res.json({newId: newId});
                });

            default:
                var err = "Could not match route /new-id/" + req.params.target;
                _handleErr(err, res);
        }
    });

    // TODO add route for userid/address to get only related tasks `/mytasks`

    // GET single
    app.get('/task/:idx', function (req, res) {
        taskManager.getTaskAtIndex(req.params.idx, function (data) {
            res.send(data);
        });
    });

    app.get('/keyatidx/:idx', function (req, res) {
        taskManager.getTaskKeyAtIndex(req.params.idx, function (data) {
            res.send(data);
        });
    });

    http.createServer(app).listen(portHTTP, function () {
        console.log('Listening for HTTP requests on port ' + portHTTP + '.');
    });
};

module.exports = {init: init};
<commit_msg>Adds `/user/signup` route handler and `user` case for `assignNewId`<commit_after>var express = require('express');
var http = require('http');
var bodyParser = require('body-parser');
var async = require('async');

var logger = require(__libs+'/eris/eris-logger');
var taskManager = require(__js+'/taskManager');
var userManager = require(__js+'/userManager');

var init = function () {

    var log = logger.getLogger('eris.chain.server');

    var portHTTP = process.env.IDI_PORT || __settings.eris.server.port_http || 8082;
    var app = express();

    // Configure PORTAL
    // app.use('/'+(__settings.eris.server.contextPath || 'hello-eris'), express.static(__dirname + '/ui'));

    // Configure JSON parsing as default
    app.use(bodyParser.json());

    /**
     * _handleErr - description
     *
     * @param  {type} err description
     * @param  {type} res description
     * @return {type}     description
     */
    function _handleErr (err, res) {
        if (err) {
            log.error(err.stack);
            res.sendStatus(500);
        }
    }

    /**
     * ROUTING
     */

    // GET all available task objects
    app.get('/tasks', function (req, res) {
        async.waterfall([
            // Get all available task contract addresses
            taskManager.getAllTaskAddresses,
            // Map each address to its task contract and callback an array of task objects
            function (addresses, callback) {
                async.map(addresses, taskManager.getTaskAtAddress, function (err, tasks) {
                    callback(err, tasks);
                });
            }
        ], function (err, tasks) {
            _handleErr(err, res);
            log.info("GET /tasks: ", tasks);
            res.json({data: tasks});
        });
    });

    // POST new task
    app.post('/tasks', function (req, res) {
        var task = req.body;

        log.debug("POST task: ", task);
        taskManager.addTask(task, function (err, address) {
            _handleErr(err, res);
            res.send(address);
        });
    });

    // GET single
    app.get('/task/:idx', function (req, res) {
        taskManager.getTaskAtIndex(req.params.idx, function (data) {
            res.send(data);
        });
    });

    // ############################

    app.post('/user/signup', function (req, res) {
        var user = req.body;

        log.debug("POST /user/signup: ", user);
        userManager.addUser(user, function (err, address) {
            _handleErr(err, res);
            res.json({address: address});
        });
    });

    // TODO not DRY, abstract this further
    app.get('/new-id/:target', function (req, res) {
        var newId;

        log.debug("GET /new-id/" + req.params.target);
        switch (req.params.target) {
            case 'task':
                return taskManager.getTaskListSize(function (err, size) {
                    _handleErr(err, res);
                    // increment size by one to mint a new id number & turn it back into string type
                    newId = String(Number(size) + 1);
                    res.json({newId: newId});
                });

            case 'user':
                return userManager.getUserListSize(function (err, size) {
                    _handleErr(err, res);
                    // increment size by one to mint a new id number & turn it back into string type
                    newId = String(Number(size) + 1);
                    res.json({newId: newId});
                });

            default:
                var err = "Could not match route /new-id/" + req.params.target;
                return _handleErr(err, res);
        }
    });

    // TODO add route for userid/address to get only related tasks `/mytasks`

    app.get('/keyatidx/:idx', function (req, res) {
        taskManager.getTaskKeyAtIndex(req.params.idx, function (data) {
            res.send(data);
        });
    });

    http.createServer(app).listen(portHTTP, function () {
        console.log('Listening for HTTP requests on port ' + portHTTP + '.');
    });
};

module.exports = {init: init};
<|endoftext|>


<commit_before>/*
MIT License

Copyright (c) 2016 José Rafael Vieira

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/

(function() {

"use strict";

	if(!console){
		console = {
			log: function(){},
			warn: function(){}
		}
	}

	Object.defineProperties(Object.prototype, {
		findloop: {
			value: function (a){
				var arr = a || [this];
				if(this.chainage){
					for(var i = 0; i < this.chainage.length; i++){
						if(arr.indexOf(this.chainage[i])>-1){
							console.warn('Looping',this.chainage[i]);	throw new Error('loop in chain tree');
						}else{
							arr.push(this.chainage[i]);
							this.chainage[i].findloop(arr);
						}
					}
				}else{
					return false;
				}
			}
		},
		chain: {
			value: function (){
				if(!this.chainage){
					this.chainage = [];
				}
				for(var i = 0; i < arguments.length; i++){
					this.chainage.push(arguments[i]);
					if(Object.prototype.chain.allowloops == false){
						this.findloop();
					}
				}
				return this;
			}
		},
		get: {
			value: function (p,own){
				var prop = this[p];
				var that = this;
				if(this.chainage){
					var i = 0;
					while(typeof prop === 'undefined' && i < this.chainage.length){
						if(!own){that = this.chainage[i]}
						prop = this.chainage[i][p];
						i ++;
					}
				}
				if(prop instanceof Function){
					return function (){
						var args = [];
						for(var i = 0; i < arguments.length; i ++){
							args.push(arguments[i]);
						}
						return prop.apply(that,args);
					}
				}
				return prop;
			}
		},
		getget: {
			value: function (p,own,recursion){
				if(typeof p != 'string'){throw new Error('Getget: property name cannot be '+p+'.');}
				if(Object.prototype.chain.allowloops == true){console.warn('Getget called while chain.allowloops is set to '+chain.allowloops+'.')}
				var prop = this[p];
				var that = (recursion && own)?own:this;
				if(this.chainage){
					var i = 0;
					while(typeof prop === 'undefined' && i < this.chainage.length){
						if(!own){that = this.chainage[i]}
						prop = this.chainage[i].getget(p,(own?that:false),true);
						i ++;
					}
				}
				if(prop instanceof Function){
					return function (){
						var args = [];
						for(var i = 0; i < arguments.length; i ++){
							args.push(arguments[i]);
						}
						return prop.apply(that,args);
					}
				}
				return prop;
			}
		},
		raw: {
			value: function (p,own){
				var raw = [this[p]];
				var that = [this];
				if(this.chainage){
					for(var i = 0; i < this.chainage.length; i ++){
						own ? that.push(this) : that.push(this.chainage[i]);
						raw.push(this.chainage[i][p]);
					}
				}
				var raw2 = [];
				for(var ii = 0; ii < raw.length; ii ++){
					if(raw[ii] instanceof Function){
						var meth = raw[ii];
						var vic = that[ii];
						raw2.push(function (){
							var args = [];
							for(var i = 0; i < arguments.length; i ++){
								args.push(arguments[i]);
							}
							return meth.apply(vic,args);
						});
					}else{
						raw2.push(raw[ii]);
					}
				}
				return raw2;
			}
		},
		rawraw: {
			value: function (p,own){
				if(typeof p != 'string'){throw new Error('Rawraw: property name cannot be '+p+'.');}
				var args = [];
				args.push(this.getget(p,own?this:false,true));
				if(this.chainage){
					for(var i = 0; i < this.chainage.length; i ++){
						args.push(this.chainage[i].getget(p,own?this:false,true));
					}
				}
				return args;
			}
		},
		dechain: {
			value: function (){
				if(this.chainage){
					if(!arguments.length){
						this.chainage = [];
					}else{
						for(var i = 0; i < arguments.length; i ++){
							while(this.chainage.indexOf(arguments[i]) > -1){
								this.chainage.splice(this.chainage.indexOf(arguments[i]),1);
							}
						}
					}
				}
				return this;
			}
		},
		rechain: {
			value: function (x,o){
				if(!this.chainage){
					this.chainage = [];
				}
				if(!(this.chainage.indexOf(x) < 0)){
					this.chainage.splice(this.chainage.indexOf(x),1,o);
				}
				return this;
			}
		},
		inchain: {
			value: function (){
				if(!this.chainage){
					this.chainage = [];
				}
				for(var i = arguments.length; i > 0; i --){
					this.chainage.unshift(arguments[i-1]);
				}
				return this;
			}
		}
	})

	Object.prototype.chain.allowloops = false;

}());
<commit_msg>manipulation methods test<commit_after>/*
MIT License

Copyright (c) 2016 José Rafael Vieira

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/

/*TODO
OK	chain
OK	dechain
OK	inchain
ok	rechain
	get
	getget
	raw
	rawraw
	findloop-allowloops

	module export

	test rechain for multiple instances of same obhect in chain
	make warning when multiple instances of same object in chain
*/

module.exports = chain;

'use strict';

const ch_ = {
	//adds objects to chain
	add: function () {
		this.ch = this.ch || []; //guarantees chain array
		for (let i = 0; i < arguments.length; i ++) { //adds objects to chain
			this.ch.push(arguments[i]);
		}
		//FINDLOOP
		return this;
	},
	//removes objects from chain
	rem: function () {
		if(this.ch){
			if(!arguments.length){
				this.ch = [];
			}else{
				for(let i = 0; i < arguments.length; i ++){
					while(this.ch.indexOf(arguments[i]) > -1){
						this.ch.splice(this.ch.indexOf(arguments[i]),1);
					}
				}
			}
		}
		return this;
	},
	//prepends objects to chain
	pre: function () {
		this.ch = this.ch || []; //guarantees chain array
		for (let i = arguments.length; i > 0; i --) { //prepends objects to chain
			this.ch.unshift(arguments[i]);
		}
		//FINDLOOP
		return this;
	},
	//replaces objects in chain
	rep: function (x,o) {
		this.ch = this.ch || []; //guarantees chain array
		if(!(this.ch.indexOf(x) < 0)){ //replaces objects in chain
			this.ch.splice(this.ch.indexOf(x),1,o);
		}
		//test this for multiple instances of same object in chain
		//FINDLOOP
		return this;
	}
}

//prepares object for chain
function chain (o,oo) { //object, object or array of objects to add to chain
	o.ch = []; //resets and attributes chain array
	if (oo) { //adds oo to chain
		(oo instanceof Array) ? o.ch = o.ch.concat(oo) : o.ch.push(oo);
	}
	//adds chain methods to o
	o.chain = ch_.add;
	o.dechain = ch_.rem;
	o.inchain = ch_.pre;
	o.rechain = ch_.rep;

	return o;
}
<|endoftext|>


<commit_before>/**
 * Telegram API Services
 *
 * @description :: Server-side logic for managing Telegram's BOT Updates
 * @author      :: Alejandro González - algope@github
 * @licence     :: The MIT License (MIT)
 *
 */

var querystring = require('querystring');
var https = require('https');
var request = require('request');

module.exports.sendMessage = function (chat_id, text, parse_mode, disable_web_page_preview, reply_to_message_id, reply_markup) {
    var options = {
        host: sails.config.telegram.url,
        path: "/bot" + sails.config.telegram.token + '/sendMessage',
        method: 'POST',
        headers: {
            'Content-Type': 'application/json'
        }
    };
    var post_data = JSON.stringify({
        chat_id: chat_id,
        text: text,
        parse_mode: parse_mode,
        disable_web_page_preview: disable_web_page_preview,
        reply_to_message_id: reply_to_message_id,
        reply_markup: reply_markup
    });

    return new Promise(function (resolve, reject) {
        var postReq = https.request(options, function (res) {
            res.setEncoding('utf8');
            var json = "";
            res.on('data', function (chunk) {
                json += chunk;
            });
            res.on('end', function () {
                resolve(JSON.parse(json))
            });
        });
        postReq.write(post_data);
        postReq.end();
    });
};

module.exports.setWebHook = function (url) {
    return new Promise(function (resolve, reject) {
        var formData = {
            url: url
        };
        request.post({
            url: 'https://' + sails.config.telegram.url + '/bot' + sails.config.telegram.token + '/setWebHook',
            formData: formData
        }, function (err, httpResponse, body) {
            if (err) {
                reject(err);
            }
            resolve(JSON.parse(body))
        });

    })
};

module.exports.getFile = function (file_id) {
    sails.log.debug(">>>> I'm at getFile");
    var options = {
        host: sails.config.telegram.url,
        path: '/bot' + sails.config.telegram.token + '/getFile?file_id='+file_id
    };
    return new Promise(function (resolve, reject) {
        https.get(options, function (res) {
            var json = "";
            sails.log.debug(">>>> I'm at getFile, inside PROMISE");
            res.on('data', function (chunk) {
                json += chunk;
            });
            res.on('end', function () {
                sails.log.debug(">>>> I'm at getFile ALL GREAT!");
                resolve(JSON.parse(json));
            });
            res.on('error', function (){
                sails.log.error("ERROR ON REQUEST to getFILE TELEGRAM");
            })
        });
    })
};

module.exports.pushToS3 = function(path){
    var url = 'api.telegram.org/file/bot' + sails.config.telegram.token + path;
    var file = path.split('/');
    var file_name = file[1];
    sails.log.debug("FILE NAME FOR S3 ::::: "+file_name);
    return new Promise(function (resolve, reject){
        var streamingS3 = require('streaming-s3'),
            request = require('request');
        var rStream = request.get(url);

        var uploader = new streamingS3(rStream, {accessKeyId: sails.config.s3.accessKeyId, secretAccessKey: sails.conf.s3.secretAccessKey},
            {
                Bucket: sails.config.s3.bucket,
                Key: file_name,
                ContentType: 'image/jpeg'
            },function (err, resp, stats) {
                if (err) return console.log('Upload error: ', e);
                console.log('Upload stats: ', stats);
                console.log('Upload successful: ', resp);
            }

        );

    })
};

module.exports.downloadFile = function (file_path) {
    var options = {
        host: "api.telegram.org",
        path: "/file/bot" + sails.config.telegram.token + file_path
    };
    return new Promise(function (resolve, reject) {
        https.get(options, function (res) {
            var json = "";
            res.on('data', function (chunk) {
                json += chunk;
            });
            res.on('end', function () {
                resolve(JSON.parse(json));
            });
        });
    })
};
<commit_msg>Testing push to S3<commit_after>/**
 * Telegram API Services
 *
 * @description :: Server-side logic for managing Telegram's BOT Updates
 * @author      :: Alejandro González - algope@github
 * @licence     :: The MIT License (MIT)
 *
 */

var querystring = require('querystring');
var https = require('https');
var request = require('request');

module.exports.sendMessage = function (chat_id, text, parse_mode, disable_web_page_preview, reply_to_message_id, reply_markup) {
    var options = {
        host: sails.config.telegram.url,
        path: "/bot" + sails.config.telegram.token + '/sendMessage',
        method: 'POST',
        headers: {
            'Content-Type': 'application/json'
        }
    };
    var post_data = JSON.stringify({
        chat_id: chat_id,
        text: text,
        parse_mode: parse_mode,
        disable_web_page_preview: disable_web_page_preview,
        reply_to_message_id: reply_to_message_id,
        reply_markup: reply_markup
    });

    return new Promise(function (resolve, reject) {
        var postReq = https.request(options, function (res) {
            res.setEncoding('utf8');
            var json = "";
            res.on('data', function (chunk) {
                json += chunk;
            });
            res.on('end', function () {
                resolve(JSON.parse(json))
            });
        });
        postReq.write(post_data);
        postReq.end();
    });
};

module.exports.setWebHook = function (url) {
    return new Promise(function (resolve, reject) {
        var formData = {
            url: url
        };
        request.post({
            url: 'https://' + sails.config.telegram.url + '/bot' + sails.config.telegram.token + '/setWebHook',
            formData: formData
        }, function (err, httpResponse, body) {
            if (err) {
                reject(err);
            }
            resolve(JSON.parse(body))
        });

    })
};

module.exports.getFile = function (file_id) {
    sails.log.debug(">>>> I'm at getFile");
    var options = {
        host: sails.config.telegram.url,
        path: '/bot' + sails.config.telegram.token + '/getFile?file_id='+file_id
    };
    sails.log.debug("OPTIONS.host"+options.host);
    sails.log.debug("OPTIONS.path"+path);

    return new Promise(function (resolve, reject) {
        https.get(options, function (res) {
            var json = "";
            sails.log.debug(">>>> I'm at getFile, inside PROMISE");
            res.on('data', function (chunk) {
                json += chunk;
            });
            res.on('end', function () {
                sails.log.debug(">>>> I'm at getFile ALL GREAT!");
                resolve(JSON.parse(json));
            });
            res.on('error', function (){
                sails.log.error("ERROR ON REQUEST to getFILE TELEGRAM");
            })
        });
    })
};

module.exports.pushToS3 = function(path){
    var url = 'api.telegram.org/file/bot' + sails.config.telegram.token + path;
    var file = path.split('/');
    var file_name = file[1];
    sails.log.debug("FILE NAME FOR S3 ::::: "+file_name);
    return new Promise(function (resolve, reject){
        var streamingS3 = require('streaming-s3'),
            request = require('request');
        var rStream = request.get(url);

        var uploader = new streamingS3(rStream, {accessKeyId: sails.config.s3.accessKeyId, secretAccessKey: sails.conf.s3.secretAccessKey},
            {
                Bucket: sails.config.s3.bucket,
                Key: file_name,
                ContentType: 'image/jpeg'
            },function (err, resp, stats) {
                if (err) return console.log('Upload error: ', e);
                console.log('Upload stats: ', stats);
                console.log('Upload successful: ', resp);
            }

        );

    })
};

module.exports.downloadFile = function (file_path) {
    var options = {
        host: "api.telegram.org",
        path: "/file/bot" + sails.config.telegram.token + file_path
    };
    return new Promise(function (resolve, reject) {
        https.get(options, function (res) {
            var json = "";
            res.on('data', function (chunk) {
                json += chunk;
            });
            res.on('end', function () {
                resolve(JSON.parse(json));
            });
        });
    })
};
<|endoftext|>


<commit_before>/**
 * Base class for creating other shapes.
 * Each custom shape must extends from this class.
 *
 * @since 1.0.0
 * @example
 * import Shape from 'kittik-shape-basic';
 *
 * export default class Rectangle extends Shape {
 *   render() {
 *     const cursor = this.getCursor();
 *     // Implement your logic here for rendering the shape
 *   }
 * }
 */
export default class Shape {
  /**
   * Create basic Shape instance.
   * This shape renders nothing, but throws an exception that you need to implement this shape in childes.
   *
   * @constructor
   * @param {Cursor} cursor Cursor instance used for render the shape
   * @param {Object} [options] Options object
   * @param {String} [options.text] Text that will be rendered in the shape
   * @param {Number|String} [options.width] Shape width can be 100 (cells) or 100%
   * @param {Number|String} [options.height] Shape height can be 100 (cells) or 100%
   * @param {Number|String} [options.x] Absolute coordinate X can be 100 (cells), left, center, right or percents
   * @param {Number|String} [options.y] Absolute coordinate Y can be 100 (cells), top, middle, bottom or percents
   * @param {String|Boolean} [options.background] Background color can be color name, rgb, hex or false it you want to disable
   * @param {String|Boolean} [options.foreground] Foreground color can be color name, rgb, hex or false it you want to disable
   * @example
   * Shape.create(cursor, {
   *   text: 'Hello, World',
   *   width: '50%',
   *   height: '50%',
   *   x: 'center',
   *   y: 'middle',
   *   background: 'black',
   *   foreground: 'white'
   * });
   */
  constructor(cursor, options = {}) {
    this.setCursor(cursor);
    this.setText(options.text);
    this.setWidth(options.width);
    this.setHeight(options.height);
    this.setX(options.x);
    this.setY(options.y);
    this.setBackground(options.background);
    this.setForeground(options.foreground);
  }

  /**
   * Get option value.
   *
   * @param {String} path Path can be set with dot-notation
   * @returns {*}
   * @example
   * shape.get('my.options.object.value');
   */
  get(path) {
    return path.split('.').reduce((obj, key) => obj && obj[key], this);
  }

  /**
   * Set new option value.
   *
   * @param {String} path Path can be set with dot-notation
   * @param {*} value Value that need to be written to the options object
   * @returns {Shape}
   * @example
   * shape.set('my.options.object.value', 'value');
   */
  set(path, value) {
    let obj = this;
    let tags = path.split('.');
    let len = tags.length - 1;

    for (let i = 0; i < len; i++) {
      if (typeof obj[tags[i]] === 'undefined') obj[tags[i]] = {};
      obj = obj[tags[i]];
    }

    obj[tags[len]] = value;

    return this;
  }

  /**
   * Get cursor that used for render this shape.
   *
   * @returns {Cursor}
   */
  getCursor() {
    return this.get('cursor');
  }

  /**
   * Assign cursor to the shape which will be used for render this shape.
   *
   * @param {Cursor} cursor
   * @returns {Shape}
   */
  setCursor(cursor) {
    return this.set('cursor', cursor);
  }

  /**
   * Get text content from this shape.
   *
   * @returns {String}
   */
  getText() {
    return this.get('text');
  }

  /**
   * Set new text content to this shape.
   *
   * @param {String} [text=''] New text
   * @returns {Shape}
   */
  setText(text = '') {
    return this.set('text', text);
  }

  /**
   * Get shape width.
   *
   * @returns {Number}
   */
  getWidth() {
    const width = this.get('width');

    if (/\d+%$/.test(width)) return Math.floor(width.slice(0, -1) * this.getCursor()._width / 100);

    return width;
  }

  /**
   * Set new shape width.
   *
   * @param {Number|String} [width=15] Shape width
   * @returns {Shape}
   * @example
   * shape.setWidth(15); // shape width is equal to 15 cells in the terminal
   * shape.setWidth('20%'); // shape width is equal to 20% of total viewport width
   */
  setWidth(width = 15) {
    return this.set('width', width);
  }

  /**
   * Get shape height.
   *
   * @returns {Number}
   */
  getHeight() {
    const height = this.get('height');

    if (/\d+%$/.test(height)) return Math.floor(height.slice(0, -1) * this.getCursor()._height / 100);

    return height;
  }

  /**
   * Set new shape height.
   *
   * @param {Number|String} [height=5] Shape height
   * @returns {Shape}
   * @example
   * shape.setHeight(15); // shape height is equal to 15 cells in the terminal
   * shape.setHeight('20%'); // shape height is equal to 20% of total viewport height
   */
  setHeight(height = 5) {
    return this.set('height', height);
  }

  /**
   * Get X coordinate.
   *
   * @returns {Number}
   */
  getX() {
    const x = this.get('x');

    if (x === 'left') return 0;
    if (x === 'center') return Math.floor(this.getCursor()._width / 2 - this.getWidth() / 2);
    if (x === 'right') return Math.floor(this.getCursor()._width - this.getWidth());
    if (/\d+%$/.test(x)) return Math.floor(x.slice(0, -1) * this.getCursor()._width / 100);

    return x;
  }

  /**
   * Set X coordinate.
   *
   * @param {Number|String} [x=10]
   * @returns {Shape}
   * @example
   * shape.setX(2); // move shape to third cell by X axis
   * shape.setX('left'); // align shape to the left
   * shape.setX('center'); // align shape in the center
   * shape.setX('right'); // align shape to the right
   * shape.setX('50%'); // move shape to 50% by X axis
   */
  setX(x = 10) {
    return this.set('x', x);
  }

  /**
   * Get Y coordinate.
   *
   * @returns {Number}
   */
  getY() {
    const y = this.get('y');

    if (y === 'top') return 0;
    if (y === 'middle') return Math.floor(this.getCursor()._height / 2 - this.getHeight() / 2);
    if (y === 'bottom') return Math.floor(this.getCursor()._height - this.getHeight());
    if (/\d+%$/.test(y)) return Math.floor(y.slice(0, -1) * this.getCursor()._height / 100);

    return y;
  }

  /**
   * Set Y coordinate.
   *
   * @param {Number|String} [y=10]
   * @returns {Shape}
   * @example
   * shape.setY(2); // move shape to third cell by Y axis
   * shape.setY('top'); // align shape to the top
   * shape.setY('middle'); // align shape in the middle
   * shape.setY('bottom'); // align shape to the bottom
   * shape.setY('50%'); // move shape to 50% by Y axis
   */
  setY(y = 10) {
    return this.set('y', y);
  }

  /**
   * Get background color.
   *
   * @returns {String|Boolean}
   */
  getBackground() {
    return this.get('background');
  }

  /**
   * Set new background color.
   *
   * @param {String|Boolean} [background=false] Color name, rgb, hex or false if you want to disable background
   * @returns {Shape}
   * @example
   * shape.setBackground('black');
   * shape.setBackground('#AABBCC');
   * shape.setBackground('rgb(0, 100, 200)');
   * shape.setBackground(false);
   */
  setBackground(background = false) {
    return this.set('background', background);
  }

  /**
   * Get foreground color.
   *
   * @returns {String|Boolean}
   */
  getForeground() {
    return this.get('foreground');
  }

  /**
   * Set new foreground color.
   *
   * @param {String|Boolean} [foreground = false] Color name, rgb, hex or false if you want to disable foreground
   * @returns {Shape}
   * @example
   * shape.setForeground('black');
   * shape.setForeground('#AABBCC');
   * shape.setForeground('rgb(0, 100, 200)');
   * shape.setForeground(false);
   */
  setForeground(foreground = false) {
    return this.set('foreground', foreground);
  }

  /**
   * Base render method that must be implemented in childes.
   *
   * @abstract
   * @throws {Error} Throws error if method is not overridden
   */
  render() {
    throw new Error('render() method must be implemented');
  }

  /**
   * Returns Object representation of the shape.
   * This representation consists of all options fields that you can pass in the constructor.
   *
   * @returns {Object}
   */
  toObject() {
    return {
      type: this.constructor.name,
      options: {
        text: this.get('text'),
        width: this.get('width'),
        height: this.get('height'),
        x: this.get('x'),
        y: this.get('y'),
        background: this.get('background'),
        foreground: this.get('foreground')
      }
    };
  }

  /**
   * Returns JSON representation of the shape.
   *
   * @returns {JSON}
   */
  toJSON() {
    return JSON.stringify(this.toObject());
  }

  /**
   * Wrapper around `new Shape()`.
   *
   * @static
   * @param {*} args
   * @returns {Shape}
   */
  static create(...args) {
    return new this(...args);
  }

  /**
   * Creates new Shape instance from Object representation.
   * You can ignore cursor param and create only Shape representation.
   * Though, you can add cursor in the runtime via {@link setCursor} method.
   *
   * @static
   * @param {Object} obj Object that you got from {@link toObject} method
   * @param {Cursor} [cursor] Cursor instance
   * @returns {Shape}
   * @throws {Error} Throws an error if object is not a representation of the shape
   */
  static fromObject(obj, cursor) {
    if (!obj.type || !obj.options) throw new Error('It looks like it is not an Object representation of the shape');
    if (obj.type !== this.name) throw new Error(`${obj.type} is not an Object representation of the ${this.name}`);

    return this.create(cursor, obj.options);
  }

  /**
   * Creates new Shape instance from JSON representation.
   * You can ignore cursor param and create only Shape representation.
   * Though, you can add cursor in the runtime via {@link setCursor} method.
   *
   * @static
   * @param {String} json JSON string that you got from {@link Shape.toJSON}
   * @param {Cursor} [cursor] Cursor instance
   * @returns {Shape}
   */
  static fromJSON(json, cursor) {
    return this.fromObject(JSON.parse(json), cursor);
  }
}
<commit_msg>fix(shape): Update kittik-cursor to version with 256-color palette<commit_after>/**
 * Base class for creating other shapes.
 * Each custom shape must extends from this class.
 *
 * @since 1.0.0
 * @example
 * import Shape from 'kittik-shape-basic';
 *
 * export default class Rectangle extends Shape {
 *   render() {
 *     const cursor = this.getCursor();
 *     // Implement your logic here for rendering the shape
 *   }
 * }
 */
export default class Shape {
  /**
   * Create basic Shape instance.
   * This shape renders nothing, but throws an exception that you need to implement this shape in childes.
   *
   * @constructor
   * @param {Cursor} cursor Cursor instance used for render the shape
   * @param {Object} [options] Options object
   * @param {String} [options.text] Text that will be rendered in the shape
   * @param {Number|String} [options.width] Shape width can be 100 (cells) or 100%
   * @param {Number|String} [options.height] Shape height can be 100 (cells) or 100%
   * @param {Number|String} [options.x] Absolute coordinate X can be 100 (cells), left, center, right or percents
   * @param {Number|String} [options.y] Absolute coordinate Y can be 100 (cells), top, middle, bottom or percents
   * @param {String} [options.background] Background color can be only color name or `none` if you want to disable
   * @param {String} [options.foreground] Foreground color can be only color name or `none` if you want to disable
   * @example
   * Shape.create(cursor, {
   *   text: 'Hello, World',
   *   width: '50%',
   *   height: '50%',
   *   x: 'center',
   *   y: 'middle',
   *   background: 'black',
   *   foreground: 'none'
   * });
   */
  constructor(cursor, options = {}) {
    this.setCursor(cursor);
    this.setText(options.text);
    this.setWidth(options.width);
    this.setHeight(options.height);
    this.setX(options.x);
    this.setY(options.y);
    this.setBackground(options.background);
    this.setForeground(options.foreground);
  }

  /**
   * Get option value.
   *
   * @param {String} path Path can be set with dot-notation
   * @returns {*}
   * @example
   * shape.get('my.options.object.value');
   */
  get(path) {
    return path.split('.').reduce((obj, key) => obj && obj[key], this);
  }

  /**
   * Set new option value.
   *
   * @param {String} path Path can be set with dot-notation
   * @param {*} value Value that need to be written to the options object
   * @returns {Shape}
   * @example
   * shape.set('my.options.object.value', 'value');
   */
  set(path, value) {
    let obj = this;
    let tags = path.split('.');
    let len = tags.length - 1;

    for (let i = 0; i < len; i++) {
      if (typeof obj[tags[i]] === 'undefined') obj[tags[i]] = {};
      obj = obj[tags[i]];
    }

    obj[tags[len]] = value;

    return this;
  }

  /**
   * Get cursor that used for render this shape.
   *
   * @returns {Cursor}
   */
  getCursor() {
    return this.get('cursor');
  }

  /**
   * Assign cursor to the shape which will be used for render this shape.
   *
   * @param {Cursor} cursor
   * @returns {Shape}
   */
  setCursor(cursor) {
    return this.set('cursor', cursor);
  }

  /**
   * Get text content from this shape.
   *
   * @returns {String}
   */
  getText() {
    return this.get('text');
  }

  /**
   * Set new text content to this shape.
   *
   * @param {String} [text=''] New text
   * @returns {Shape}
   */
  setText(text = '') {
    return this.set('text', text);
  }

  /**
   * Get shape width.
   *
   * @returns {Number}
   */
  getWidth() {
    const width = this.get('width');

    if (/\d+%$/.test(width)) return Math.floor(width.slice(0, -1) * this.getCursor()._width / 100);

    return width;
  }

  /**
   * Set new shape width.
   *
   * @param {Number|String} [width=15] Shape width
   * @returns {Shape}
   * @example
   * shape.setWidth(15); // shape width is equal to 15 cells in the terminal
   * shape.setWidth('20%'); // shape width is equal to 20% of total viewport width
   */
  setWidth(width = 15) {
    return this.set('width', width);
  }

  /**
   * Get shape height.
   *
   * @returns {Number}
   */
  getHeight() {
    const height = this.get('height');

    if (/\d+%$/.test(height)) return Math.floor(height.slice(0, -1) * this.getCursor()._height / 100);

    return height;
  }

  /**
   * Set new shape height.
   *
   * @param {Number|String} [height=5] Shape height
   * @returns {Shape}
   * @example
   * shape.setHeight(15); // shape height is equal to 15 cells in the terminal
   * shape.setHeight('20%'); // shape height is equal to 20% of total viewport height
   */
  setHeight(height = 5) {
    return this.set('height', height);
  }

  /**
   * Get X coordinate.
   *
   * @returns {Number}
   */
  getX() {
    const x = this.get('x');

    if (x === 'left') return 0;
    if (x === 'center') return Math.floor(this.getCursor()._width / 2 - this.getWidth() / 2);
    if (x === 'right') return Math.floor(this.getCursor()._width - this.getWidth());
    if (/\d+%$/.test(x)) return Math.floor(x.slice(0, -1) * this.getCursor()._width / 100);

    return x;
  }

  /**
   * Set X coordinate.
   *
   * @param {Number|String} [x=10]
   * @returns {Shape}
   * @example
   * shape.setX(2); // move shape to third cell by X axis
   * shape.setX('left'); // align shape to the left
   * shape.setX('center'); // align shape in the center
   * shape.setX('right'); // align shape to the right
   * shape.setX('50%'); // move shape to 50% by X axis
   */
  setX(x = 10) {
    return this.set('x', x);
  }

  /**
   * Get Y coordinate.
   *
   * @returns {Number}
   */
  getY() {
    const y = this.get('y');

    if (y === 'top') return 0;
    if (y === 'middle') return Math.floor(this.getCursor()._height / 2 - this.getHeight() / 2);
    if (y === 'bottom') return Math.floor(this.getCursor()._height - this.getHeight());
    if (/\d+%$/.test(y)) return Math.floor(y.slice(0, -1) * this.getCursor()._height / 100);

    return y;
  }

  /**
   * Set Y coordinate.
   *
   * @param {Number|String} [y=10]
   * @returns {Shape}
   * @example
   * shape.setY(2); // move shape to third cell by Y axis
   * shape.setY('top'); // align shape to the top
   * shape.setY('middle'); // align shape in the middle
   * shape.setY('bottom'); // align shape to the bottom
   * shape.setY('50%'); // move shape to 50% by Y axis
   */
  setY(y = 10) {
    return this.set('y', y);
  }

  /**
   * Get background color.
   *
   * @returns {String}
   */
  getBackground() {
    return this.get('background');
  }

  /**
   * Set new background color.
   *
   * @param {String} [background = none] Color name or `none` if you want to disable background
   * @returns {Shape}
   * @example
   * shape.setBackground('black');
   * shape.setBackground('none');
   */
  setBackground(background = 'none') {
    return this.set('background', background);
  }

  /**
   * Get foreground color.
   *
   * @returns {String}
   */
  getForeground() {
    return this.get('foreground');
  }

  /**
   * Set new foreground color.
   *
   * @param {String} [foreground = none] Color name or `none` if you want to disable foreground
   * @returns {Shape}
   * @example
   * shape.setForeground('black');
   * shape.setForeground('none');
   */
  setForeground(foreground = 'none') {
    return this.set('foreground', foreground);
  }

  /**
   * Base render method that must be implemented in childes.
   *
   * @abstract
   * @throws {Error} Throws error if method is not overridden
   */
  render() {
    throw new Error('render() method must be implemented');
  }

  /**
   * Returns Object representation of the shape.
   * This representation consists of all options fields that you can pass in the constructor.
   *
   * @returns {Object}
   */
  toObject() {
    return {
      type: this.constructor.name,
      options: {
        text: this.get('text'),
        width: this.get('width'),
        height: this.get('height'),
        x: this.get('x'),
        y: this.get('y'),
        background: this.get('background'),
        foreground: this.get('foreground')
      }
    };
  }

  /**
   * Returns JSON representation of the shape.
   *
   * @returns {JSON}
   */
  toJSON() {
    return JSON.stringify(this.toObject());
  }

  /**
   * Wrapper around `new Shape()`.
   *
   * @static
   * @param {*} args
   * @returns {Shape}
   */
  static create(...args) {
    return new this(...args);
  }

  /**
   * Creates new Shape instance from Object representation.
   * You can ignore cursor param and create only Shape representation.
   * Though, you can add cursor in the runtime via {@link setCursor} method.
   *
   * @static
   * @param {Object} obj Object that you got from {@link toObject} method
   * @param {Cursor} [cursor] Cursor instance
   * @returns {Shape}
   * @throws {Error} Throws an error if object is not a representation of the shape
   */
  static fromObject(obj, cursor) {
    if (!obj.type || !obj.options) throw new Error('It looks like it is not an Object representation of the shape');
    if (obj.type !== this.name) throw new Error(`${obj.type} is not an Object representation of the ${this.name}`);

    return this.create(cursor, obj.options);
  }

  /**
   * Creates new Shape instance from JSON representation.
   * You can ignore cursor param and create only Shape representation.
   * Though, you can add cursor in the runtime via {@link setCursor} method.
   *
   * @static
   * @param {String} json JSON string that you got from {@link Shape.toJSON}
   * @param {Cursor} [cursor] Cursor instance
   * @returns {Shape}
   */
  static fromJSON(json, cursor) {
    return this.fromObject(JSON.parse(json), cursor);
  }
}
<|endoftext|>


<commit_before>import https from 'https';
import { WebClient } from '@slack/client';

import {
  computeMessageRating,
  computeUserPsychoPass,
  computeChannelPsychoPass,
  NUM_USER_MESSAGES,
  NUM_CHANNEL_MESSAGES,
} from './psychoPass';
import Leaderboard from './leaderboard/Leaderboard';

/**
 * Class responsible for processing input messages. Produces a response to be
 * sent to the client when appropriate.
 */
export default class {
  /**
   * Initialize user and channel leaderboards.
   *
   * @param {DataStore} store
   */
  constructor(store) {
    this.store = store;
    this.userLeaderboard = new Leaderboard();
    this.channelLeaderboard = new Leaderboard();

    this.initializeUserLeaderboard();
    this.initializeChannelLeaderboard();
  }

  static get NUM_USER_MESSAGES() {
    return NUM_USER_MESSAGES;
  }

  static get NUM_CHANNEL_MESSAGES() {
    return NUM_CHANNEL_MESSAGES;
  }

  /**
   * Compute message ratings and a user's Psycho-Pass given an array of
   * messages.
   *
   * @public
   * @param {string[]} messages
   * @return {object} The Psycho-Pass and message ratings computed from the
   * messages.
   */
  static processUserMessages(messages) {
    const messageRatings = messages.map(computeMessageRating);
    const psychoPass = computeUserPsychoPass(messageRatings);

    return { psychoPass, messageRatings };
  }

  /**
   * Compute message ratings and a channel's Psycho-Pass given an array of
   * messages.
   *
   * @public
   * @param {string[]} messages
   * @return {object} The Psycho-Pass and message ratings computed from the
   * messages.
   */
  static processChannelMessages(messages) {
    const messageRatings = messages.map(computeMessageRating);
    const psychoPass = computeChannelPsychoPass(messageRatings);

    return { psychoPass, messageRatings };
  }

  /**
   * Initialize the user leaderboard.
   *
   * @private
   */
  initializeUserLeaderboard() {
    this.store.users.forEach(({ psychoPass }, id) => {
      this.userLeaderboard.update(id, psychoPass);
    });
  }

  /**
   * Initialize the channel leaderboard.
   *
   * @private
   */
  initializeChannelLeaderboard() {
    this.store.channels.forEach(({ psychoPass }, id) => {
      this.channelLeaderboard.update(id, psychoPass);
    });
  }

  /**
   * Process a new message.
   *
   * @public
   * @param {string} id The user id of the sender.
   * @param {string} message The body of the message.
   * @param {string} channel The id of the channel to which the message was
   * posted.
   * @param {string} timestamp
   * @return {Promise<string[]>} The responses to be sent to the client. The
   * array is empty if there are no responses.
   */
  newMessage(id, message, channel, timestamp) {
    const isChannelPublic = this.store.channels.has(channel);
    const responses = [];
    let promise = Promise.resolve();

    if (isChannelPublic) {
      this.updateChannel(channel, message, timestamp);

      promise = this.checkChannelPsychoPass(channel).then((res) => {
        if (res) {
          responses.push(res);
        }
      });

      this.updateUser(id, message, channel, timestamp);
    }

    const commandInfo = this.parseCommand(message);
    if (commandInfo) {
      switch (commandInfo.command) {
        case 'user':
          responses.unshift(this.psychoPassUser(commandInfo.id));
          break;
        case 'channel':
          responses.unshift(this.psychoPassChannel(commandInfo.id));
          break;
        case 'same channel':
          (() => {
            if (isChannelPublic) {
              responses.unshift(this.psychoPassChannel(channel));
            }
          })();
          break;
        case 'help':
          responses.unshift(this.help());
          break;
        case 'users':
          responses.unshift(this.leaderboardUsers());
          break;
        case 'channels':
          responses.unshift(this.leaderboardChannels());
      }
    }

    return promise.then(() => responses);
  }

  /**
   * Parse a command from a message.
   *
   * @private
   * @param {string} message
   * @return {object|null} An object containing information about the parsed
   * command if one is found. If no command is found return null.
   */
  parseCommand(message) {
    if (message === 'psychopass') {
      return { command: 'same channel' };
    }

    const command = 'psychopass ';
    if (message.startsWith(command)) {
      const fragment = message.substr(command.length);

      let subCommand = /^<([@#])(.{2,})>/;
      const result = subCommand.exec(fragment);
      if (result) {
        const id = result[2];
        const command = result[1] === '@' ? 'user' : 'channel';

        return { id, command };
      }

      subCommand = /^help(?:\s|$)/;
      if (subCommand.test(fragment)) {
        return { command: 'help' };
      }

      subCommand = 'leaderboard ';
      if (fragment.startsWith(subCommand)) {
        const subFragment = fragment.substr(subCommand.length);

        let command = /^users(?:\s|$)/;
        if (command.test(subFragment)) {
          return { command: 'users' };
        }

        command = /^channels(?:\s|$)/;
        if (command.test(subFragment)) {
          return { command: 'channels' };
        }
      }
    }

    return null;
  }

  /**
   * Produce help message.
   *
   * @private
   * @return {string} The help message.
   */
  help() {
    return 'The following commands are available:\n' +
      'psychopass @<username>  -  See the Psycho-Pass of a user\n' +
      'psychopass [#<channel>]  -  See the Psycho-Pass of the current channel' +
      ' or of the channel specified by the optional parameter\n' +
      'psychopass leaderboard users  -  See the lowest and highest user' +
      ' Psycho-Passes\n' +
      'psychopass leaderboard channels  -  See the lowest and highest channel' +
      ' Psycho-Passes\n' +
      'psychopass help  -  See this help message';
  }

  /**
   * Produce response for user leaderboard.
   *
   * @private
   * @return {string} The user leaderboard response.
   */
  leaderboardUsers() {
    const highest = this.userLeaderboard.getHighest();
    const lowest = this.userLeaderboard.getLowest();

    let s = 'Lowest:\n';
    lowest.forEach(({ id, value: psychoPass }, index) => {
      const name = this.store.getUserName(id);
      s += `${psychoPass}    ${name}\n`;
    });

    s += '\nHighest:\n';
    highest.forEach(({ id, value: psychoPass }, index) => {
      const name = this.store.getUserName(id);
      s += `${psychoPass}    ${name}\n`;
    });

    return s;
  }

  /**
   * Produce response for channel leaderboard.
   *
   * @private
   * @return {string} The channel leaderboard response.
   */
  leaderboardChannels() {
    const highest = this.channelLeaderboard.getHighest();
    const lowest = this.channelLeaderboard.getLowest();

    let s = 'Lowest:\n';
    lowest.forEach(({ id, value: psychoPass }, index) => {
      s += `${psychoPass}    <#${id}>\n`;
    });

    s += '\nHighest:\n';
    highest.forEach(({ id, value: psychoPass }, index) => {
      s += `${psychoPass}    <#${id}>\n`;
    });

    return s;
  }

  /**
   * Update stored data and Psycho-Pass of a user based on a new message.
   *
   * @private
   * @param {string} id The user id.
   * @param {string} message
   * @param {string} channel The id of the channel.
   * @param {string} timestamp
   */
  updateUser(id, message, channel, timestamp) {
    const rating = computeMessageRating(message);
    const info = { rating, channel, timestamp };
    const { messageInfo, psychoPass: oldPsychoPass } = this.store.users.get(id);
    const len = messageInfo.unshift(info);

    if (len > NUM_USER_MESSAGES) {
      messageInfo.pop();
    }

    const ratings = messageInfo.map(({ rating }) => rating);
    const newPsychoPass = computeUserPsychoPass(ratings);

    this.userLeaderboard.update(id, newPsychoPass, oldPsychoPass);
    this.store.users.get(id).psychoPass = newPsychoPass;
  }

  /**
   * Update channel data based on a new message.
   *
   * @private
   * @param {string} id The channel id.
   * @param {string} message
   * @param {string} timestamp
   */
  updateChannel(id, message, timestamp) {
    const rating = computeMessageRating(message);
    const info = { rating, timestamp };
    const {
      messageInfo,
      psychoPass: oldPsychoPass,
    } = this.store.channels.get(id);
    const len = messageInfo.unshift(info);

    if (len > NUM_CHANNEL_MESSAGES) {
      messageInfo.pop();
    }

    const ratings = messageInfo.map(({ rating }) => rating);
    const newPsychoPass = computeChannelPsychoPass(ratings);

    this.channelLeaderboard.update(id, newPsychoPass, oldPsychoPass);
    this.store.channels.get(id).psychoPass = newPsychoPass;
  }

  /**
   * Check if a channel's Psycho-Pass exceeds the acceptable threshold. If so,
   * produce a response.
   *
   * @private
   * @param {string} channel The channel id.
   * @return {Promise<string|null>} The response if there is one, otherwise null.
   */
  checkChannelPsychoPass(channel) {
    if (this.store.getChannelMonitorTimeout(channel) === 0) {
      const psychoPass = this.store.getChannelPsychoPass(channel);

      if (psychoPass > 100) {
        this.store.setChannelMonitorTimeout(channel, 10);
        return this.elevatedPsychoPassResponse();
      }
    } else {
      this.store.tickChannelMonitorTimeout(channel);
    }

    return Promise.resolve(null);
  }

  /**
   * Produce a response message for handling an elevated channel Psycho-Pass.
   *
   * @private
   * @return {Promise<string>} The response message.
   */
  elevatedPsychoPassResponse() {
    return Promise.resolve('Fuzzy kittens!');
  }

  /**
   * Handle a request for the Psycho-Pass of a user and produce a response.
   *
   * @private
   * @param {string} id The user id.
   * @return {string} Response to the request.
   */
  psychoPassUser(id) {
    const name = this.store.getUserName(id);
    const psychoPass = this.store.getUserPsychoPass(id);

    return `${name} has a Psycho-Pass of ${psychoPass}`;
  }

  /**
   * Handle a request for the Psycho-Pass of a channel and produce a response.
   *
   * @private
   * @param {string} id The channel id.
   * @return {string} Response to the request.
   */
  psychoPassChannel(id) {
    const psychoPass = this.store.getChannelPsychoPass(id);

    return `<#${id}> has a Psycho-Pass of ${psychoPass}`;
  }
};
<commit_msg>Integrate Giphy API into elevated PsychoPass response<commit_after>import https from 'https';
import { WebClient } from '@slack/client';

import {
  computeMessageRating,
  computeUserPsychoPass,
  computeChannelPsychoPass,
  NUM_USER_MESSAGES,
  NUM_CHANNEL_MESSAGES,
} from './psychoPass';
import Leaderboard from './leaderboard/Leaderboard';

/**
 * Class responsible for processing input messages. Produces a response to be
 * sent to the client when appropriate.
 */
export default class {
  /**
   * Initialize user and channel leaderboards.
   *
   * @param {DataStore} store
   */
  constructor(store) {
    this.store = store;
    this.userLeaderboard = new Leaderboard();
    this.channelLeaderboard = new Leaderboard();

    this.initializeUserLeaderboard();
    this.initializeChannelLeaderboard();
  }

  static get NUM_USER_MESSAGES() {
    return NUM_USER_MESSAGES;
  }

  static get NUM_CHANNEL_MESSAGES() {
    return NUM_CHANNEL_MESSAGES;
  }

  /**
   * Compute message ratings and a user's Psycho-Pass given an array of
   * messages.
   *
   * @public
   * @param {string[]} messages
   * @return {object} The Psycho-Pass and message ratings computed from the
   * messages.
   */
  static processUserMessages(messages) {
    const messageRatings = messages.map(computeMessageRating);
    const psychoPass = computeUserPsychoPass(messageRatings);

    return { psychoPass, messageRatings };
  }

  /**
   * Compute message ratings and a channel's Psycho-Pass given an array of
   * messages.
   *
   * @public
   * @param {string[]} messages
   * @return {object} The Psycho-Pass and message ratings computed from the
   * messages.
   */
  static processChannelMessages(messages) {
    const messageRatings = messages.map(computeMessageRating);
    const psychoPass = computeChannelPsychoPass(messageRatings);

    return { psychoPass, messageRatings };
  }

  /**
   * Initialize the user leaderboard.
   *
   * @private
   */
  initializeUserLeaderboard() {
    this.store.users.forEach(({ psychoPass }, id) => {
      this.userLeaderboard.update(id, psychoPass);
    });
  }

  /**
   * Initialize the channel leaderboard.
   *
   * @private
   */
  initializeChannelLeaderboard() {
    this.store.channels.forEach(({ psychoPass }, id) => {
      this.channelLeaderboard.update(id, psychoPass);
    });
  }

  /**
   * Process a new message.
   *
   * @public
   * @param {string} id The user id of the sender.
   * @param {string} message The body of the message.
   * @param {string} channel The id of the channel to which the message was
   * posted.
   * @param {string} timestamp
   * @return {Promise<string[]>} The responses to be sent to the client. The
   * array is empty if there are no responses.
   */
  newMessage(id, message, channel, timestamp) {
    const isChannelPublic = this.store.channels.has(channel);
    const responses = [];
    let promise = Promise.resolve();

    if (isChannelPublic) {
      this.updateChannel(channel, message, timestamp);

      promise = this.checkChannelPsychoPass(channel).then((res) => {
        if (res) {
          responses.push(res);
        }
      });

      this.updateUser(id, message, channel, timestamp);
    }

    const commandInfo = this.parseCommand(message);
    if (commandInfo) {
      switch (commandInfo.command) {
        case 'user':
          responses.unshift(this.psychoPassUser(commandInfo.id));
          break;
        case 'channel':
          responses.unshift(this.psychoPassChannel(commandInfo.id));
          break;
        case 'same channel':
          (() => {
            if (isChannelPublic) {
              responses.unshift(this.psychoPassChannel(channel));
            }
          })();
          break;
        case 'help':
          responses.unshift(this.help());
          break;
        case 'users':
          responses.unshift(this.leaderboardUsers());
          break;
        case 'channels':
          responses.unshift(this.leaderboardChannels());
      }
    }

    return promise.then(() => responses);
  }

  /**
   * Parse a command from a message.
   *
   * @private
   * @param {string} message
   * @return {object|null} An object containing information about the parsed
   * command if one is found. If no command is found return null.
   */
  parseCommand(message) {
    if (message === 'psychopass') {
      return { command: 'same channel' };
    }

    const command = 'psychopass ';
    if (message.startsWith(command)) {
      const fragment = message.substr(command.length);

      let subCommand = /^<([@#])(.{2,})>/;
      const result = subCommand.exec(fragment);
      if (result) {
        const id = result[2];
        const command = result[1] === '@' ? 'user' : 'channel';

        return { id, command };
      }

      subCommand = /^help(?:\s|$)/;
      if (subCommand.test(fragment)) {
        return { command: 'help' };
      }

      subCommand = 'leaderboard ';
      if (fragment.startsWith(subCommand)) {
        const subFragment = fragment.substr(subCommand.length);

        let command = /^users(?:\s|$)/;
        if (command.test(subFragment)) {
          return { command: 'users' };
        }

        command = /^channels(?:\s|$)/;
        if (command.test(subFragment)) {
          return { command: 'channels' };
        }
      }
    }

    return null;
  }

  /**
   * Produce help message.
   *
   * @private
   * @return {string} The help message.
   */
  help() {
    return 'The following commands are available:\n' +
      'psychopass @<username>  -  See the Psycho-Pass of a user\n' +
      'psychopass [#<channel>]  -  See the Psycho-Pass of the current channel' +
      ' or of the channel specified by the optional parameter\n' +
      'psychopass leaderboard users  -  See the lowest and highest user' +
      ' Psycho-Passes\n' +
      'psychopass leaderboard channels  -  See the lowest and highest channel' +
      ' Psycho-Passes\n' +
      'psychopass help  -  See this help message';
  }

  /**
   * Produce response for user leaderboard.
   *
   * @private
   * @return {string} The user leaderboard response.
   */
  leaderboardUsers() {
    const highest = this.userLeaderboard.getHighest();
    const lowest = this.userLeaderboard.getLowest();

    let s = 'Lowest:\n';
    lowest.forEach(({ id, value: psychoPass }, index) => {
      const name = this.store.getUserName(id);
      s += `${psychoPass}    ${name}\n`;
    });

    s += '\nHighest:\n';
    highest.forEach(({ id, value: psychoPass }, index) => {
      const name = this.store.getUserName(id);
      s += `${psychoPass}    ${name}\n`;
    });

    return s;
  }

  /**
   * Produce response for channel leaderboard.
   *
   * @private
   * @return {string} The channel leaderboard response.
   */
  leaderboardChannels() {
    const highest = this.channelLeaderboard.getHighest();
    const lowest = this.channelLeaderboard.getLowest();

    let s = 'Lowest:\n';
    lowest.forEach(({ id, value: psychoPass }, index) => {
      s += `${psychoPass}    <#${id}>\n`;
    });

    s += '\nHighest:\n';
    highest.forEach(({ id, value: psychoPass }, index) => {
      s += `${psychoPass}    <#${id}>\n`;
    });

    return s;
  }

  /**
   * Update stored data and Psycho-Pass of a user based on a new message.
   *
   * @private
   * @param {string} id The user id.
   * @param {string} message
   * @param {string} channel The id of the channel.
   * @param {string} timestamp
   */
  updateUser(id, message, channel, timestamp) {
    const rating = computeMessageRating(message);
    const info = { rating, channel, timestamp };
    const { messageInfo, psychoPass: oldPsychoPass } = this.store.users.get(id);
    const len = messageInfo.unshift(info);

    if (len > NUM_USER_MESSAGES) {
      messageInfo.pop();
    }

    const ratings = messageInfo.map(({ rating }) => rating);
    const newPsychoPass = computeUserPsychoPass(ratings);

    this.userLeaderboard.update(id, newPsychoPass, oldPsychoPass);
    this.store.users.get(id).psychoPass = newPsychoPass;
  }

  /**
   * Update channel data based on a new message.
   *
   * @private
   * @param {string} id The channel id.
   * @param {string} message
   * @param {string} timestamp
   */
  updateChannel(id, message, timestamp) {
    const rating = computeMessageRating(message);
    const info = { rating, timestamp };
    const {
      messageInfo,
      psychoPass: oldPsychoPass,
    } = this.store.channels.get(id);
    const len = messageInfo.unshift(info);

    if (len > NUM_CHANNEL_MESSAGES) {
      messageInfo.pop();
    }

    const ratings = messageInfo.map(({ rating }) => rating);
    const newPsychoPass = computeChannelPsychoPass(ratings);

    this.channelLeaderboard.update(id, newPsychoPass, oldPsychoPass);
    this.store.channels.get(id).psychoPass = newPsychoPass;
  }

  /**
   * Check if a channel's Psycho-Pass exceeds the acceptable threshold. If so,
   * produce a response.
   *
   * @private
   * @param {string} channel The channel id.
   * @return {Promise<string|null>} The response if there is one, otherwise
   * null.
   */
  checkChannelPsychoPass(channel) {
    if (this.store.getChannelMonitorTimeout(channel) === 0) {
      const psychoPass = this.store.getChannelPsychoPass(channel);

      if (psychoPass > 100) {
        this.store.setChannelMonitorTimeout(channel, 10);
        return this.elevatedPsychoPassResponse();
      }
    } else {
      this.store.tickChannelMonitorTimeout(channel);
    }

    return Promise.resolve(null);
  }

  /**
   * Produce a response message for handling an elevated channel Psycho-Pass.
   *
   * @private
   * @return {Promise<string>} The response message.
   */
  elevatedPsychoPassResponse() {
    const apiKey = process.env.SIBYL_GIPHY_API_KEY;
    const uri = `https://api.giphy.com/v1/gifs/random?api_key=${apiKey}`;

    return new Promise((resolve, reject) => {
      https.get(uri, (res) => {
        res.on('data', (body) => {
          resolve('Fuzzy kittens!');
        });
      }).on('error', reject);
    });
  }

  /**
   * Handle a request for the Psycho-Pass of a user and produce a response.
   *
   * @private
   * @param {string} id The user id.
   * @return {string} Response to the request.
   */
  psychoPassUser(id) {
    const name = this.store.getUserName(id);
    const psychoPass = this.store.getUserPsychoPass(id);

    return `${name} has a Psycho-Pass of ${psychoPass}`;
  }

  /**
   * Handle a request for the Psycho-Pass of a channel and produce a response.
   *
   * @private
   * @param {string} id The channel id.
   * @return {string} Response to the request.
   */
  psychoPassChannel(id) {
    const psychoPass = this.store.getChannelPsychoPass(id);

    return `<#${id}> has a Psycho-Pass of ${psychoPass}`;
  }
};
<|endoftext|>


<commit_before>'use strict';
var osmium = require('osmium');
var _ = require('underscore');
var userModel = require('./user-model');
var fs = require('fs');

module.exports = function(options, done) {
	var counterObj = {
		users: null,
		timestamp: null
	};
	var users = {};
	if (options.users) {
		options.users.forEach(function (user) {
			var u = new userModel();
			u.uid = parseInt(user.uid, 10);
			u.username = user.username;
			users[u.uid] = u;
		});
	}
	var reader = new osmium.Reader(options.filename);
	var timestamp = fs.statSync(options.filename).birthtime;
	counterObj.timestamp = timestamp;
	var handler = new osmium.Handler();

	//WAY
	handler.on('way', function(way) {
		if (!options.users) {
			if (!users[way.uid]) {
				users[way.uid] = new userModel();
				users[way.uid].username = way.user;
			}
		}
		if (users[way.uid]) {
			users[way.uid] = countVersion('ways', users[way.uid], way);
			users[way.uid].changesets.push(way.changeset);
			users = countTags(users, way);
		}
	});

	//NODE
	handler.on('node', function(node) {
		if (!options.users) {
			if (!users[node.uid]) {
				users[node.uid] = new userModel();
				users[node.uid].username = node.user;
			}
		}
		if (users[node.uid]) {
			users[node.uid] = countVersion('nodes', users[node.uid], node);
			users[node.uid].changesets.push(node.changeset);
			users = countTags(users, node);
		}
	});

	//RELATION
	handler.on('relation', function(relation) {
		if (!options.users) {
			if (!users[relation.uid]) {
				users[relation.uid] = new userModel();
				users[relation.uid].username = relation.user;
			}
		}
		if (users[relation.uid]) {
			users[relation.uid] = countVersion('relations', users[relation.uid], relation);
			users[relation.uid].changesets.push(relation.changeset);
			users = countTags(users, relation);
		}
	});
	osmium.apply(reader, handler);

	_.each(users, function(val, key) {
		val.changesets = _.uniq(val.changesets);
	});

	counterObj.users = users;
	done(null, counterObj);
};

function countVersion(type, user, obj) {
	if (!obj.visible) {
		++user[type].d;
	} else if (obj.version > 1) {
		++user[type].m;
	} else {
		++user[type].c;
	}
	return user;
}

function countTags(users, obj) {
	var tagsCount = {};
	_.each(obj.tags(), function(v, k) {
		if (tagsCount[k]) {
			if (tagsCount[k][v]) {
				tagsCount[k][v] = tagsCount[k][v] + 1;
			} else {
				tagsCount[k][v] = 1;
			}
		} else {
			tagsCount[k] = {};
			tagsCount[k][v] = 1;
		}
	});
	if (!obj.visible) {
		users[obj.uid].tags_deleted = tagsCount;
	} else if (obj.version > 1) {
		users[obj.uid].tags_modified = tagsCount;
	} else {
		users[obj.uid].tags_created = tagsCount;
	}
	return users;
}
<commit_msg>use ctime<commit_after>'use strict';
var osmium = require('osmium');
var _ = require('underscore');
var userModel = require('./user-model');
var fs = require('fs');

module.exports = function(options, done) {
	var counterObj = {
		users: null,
		timestamp: null
	};
	var users = {};
	if (options.users) {
		options.users.forEach(function (user) {
			var u = new userModel();
			u.uid = parseInt(user.uid, 10);
			u.username = user.username;
			users[u.uid] = u;
		});
	}
	var reader = new osmium.Reader(options.filename);
	var timestamp = fs.statSync(options.filename).ctime;
	counterObj.timestamp = timestamp;
	var handler = new osmium.Handler();

	//WAY
	handler.on('way', function(way) {
		if (!options.users) {
			if (!users[way.uid]) {
				users[way.uid] = new userModel();
				users[way.uid].username = way.user;
			}
		}
		if (users[way.uid]) {
			users[way.uid] = countVersion('ways', users[way.uid], way);
			users[way.uid].changesets.push(way.changeset);
			users = countTags(users, way);
		}
	});

	//NODE
	handler.on('node', function(node) {
		if (!options.users) {
			if (!users[node.uid]) {
				users[node.uid] = new userModel();
				users[node.uid].username = node.user;
			}
		}
		if (users[node.uid]) {
			users[node.uid] = countVersion('nodes', users[node.uid], node);
			users[node.uid].changesets.push(node.changeset);
			users = countTags(users, node);
		}
	});

	//RELATION
	handler.on('relation', function(relation) {
		if (!options.users) {
			if (!users[relation.uid]) {
				users[relation.uid] = new userModel();
				users[relation.uid].username = relation.user;
			}
		}
		if (users[relation.uid]) {
			users[relation.uid] = countVersion('relations', users[relation.uid], relation);
			users[relation.uid].changesets.push(relation.changeset);
			users = countTags(users, relation);
		}
	});
	osmium.apply(reader, handler);

	_.each(users, function(val, key) {
		val.changesets = _.uniq(val.changesets);
	});

	counterObj.users = users;
	done(null, counterObj);
};

function countVersion(type, user, obj) {
	if (!obj.visible) {
		++user[type].d;
	} else if (obj.version > 1) {
		++user[type].m;
	} else {
		++user[type].c;
	}
	return user;
}

function countTags(users, obj) {
	var tagsCount = {};
	_.each(obj.tags(), function(v, k) {
		if (tagsCount[k]) {
			if (tagsCount[k][v]) {
				tagsCount[k][v] = tagsCount[k][v] + 1;
			} else {
				tagsCount[k][v] = 1;
			}
		} else {
			tagsCount[k] = {};
			tagsCount[k][v] = 1;
		}
	});
	if (!obj.visible) {
		users[obj.uid].tags_deleted = tagsCount;
	} else if (obj.version > 1) {
		users[obj.uid].tags_modified = tagsCount;
	} else {
		users[obj.uid].tags_created = tagsCount;
	}
	return users;
}
<|endoftext|>


<commit_before>/**
 * Stack.js - JavaScript OO Implementation of Stack Data Structure
 */

 /**
 * A stack is essentially a list of data elements which may be accessed at only one end.
 * Stacks are last-in, first-out (LIFO) data structures. Elements may be added to a stack
 * with the 'push' operation and removed from a stack with the 'pop' operation. The element
 * at the top of the stack may be viewed with the 'peek' operation; this operation returns
 * the element without removing it from the stack. The 'clear' operation 'deletes' the contents
 * of the stack. The 'getLength' operation returns the length (number of elements contained)
 * in the stack. The 'toString' operation returns a string representation of the stack.
 * @class
 *
 */

var Stack = (function() {

	/**
	 * Constructor function declares and/or intialises stack properties
	 * @constructor
	 */
	function Stack() {
		this.datastore = []; // An array is the underlying data structure
		this.top = 0;// The top of the stack
	}

	/**
	 * Push new element onto stack. When the caller pushes a new element
	 * onto the stack it is stored in the top position identified by the top
	 * variable and the latter is incremented so that the new top is the next
	 * vacant position in the array.
	 * @param element Element to be added to stack
	 */
	Stack.prototype.push = function(element) {
		this.datastore[this.top++] = element;
	}

	/**
	 * Pop element off the stack. This method is essentially the reverse of the push() function
	 * returning the element in the top position of the stack and decrementing the top variable
	 * @return {*} Return the element at the top of the stack.
	 */
	Stack.prototype.pop = function() {
		this.datastore.pop(); // use Array.prototype.pop to remove last element of datastore
		return this.datastore[--this.top];
	}

	/**
	 * Return the top element of the stack by accessing the element at the top-1 position of the
	 * underlying datastore (array).
	 * @return {*} Return the top element of the stack or undefined if the stack is empty
	 */
	Stack.prototype.peek = function() {
		return this.datastore[this.top-1];
	}

	/**
	 * Reset the stack and reinitialise the underlying datastore.
	 */
	Stack.prototype.clear = function() {
		this.datastore = [];
		this.top = 0;
	}

	/**
	 * Get the number of elements in the stack.
	 * @return {number} Return the number of elements in the stack.
	 */
	Stack.prototype.getLength = function() {
		return this.top;
	};

	/**
	 * Get a string representation of the stack.
	 * @return {string} Return a string representation of the stack.
	 */
	Stack.prototype.toString = function() {
		return this.datastore.toString();
	}

	return Stack;
}());<commit_msg>Revise pop() Method to Handle Popping off an Empty Stack<commit_after>/**
 * Stack.js - JavaScript OO Implementation of Stack Data Structure
 */

 /**
 * A stack is essentially a list of data elements which may be accessed at only one end.
 * Stacks are last-in, first-out (LIFO) data structures. Elements may be added to a stack
 * with the 'push' operation and removed from a stack with the 'pop' operation. The element
 * at the top of the stack may be viewed with the 'peek' operation; this operation returns
 * the element without removing it from the stack. The 'clear' operation 'deletes' the contents
 * of the stack. The 'getLength' operation returns the length (number of elements contained)
 * in the stack. The 'toString' operation returns a string representation of the stack.
 * @class
 *
 */

var Stack = (function() {

	/**
	 * Constructor function declares and/or intialises stack properties
	 * @constructor
	 */
	function Stack() {
		this.datastore = []; // An array is the underlying data structure
		this.top = 0;// The top of the stack
	}

	/**
	 * Push new element onto stack. When the caller pushes a new element
	 * onto the stack it is stored in the top position identified by the top
	 * variable and the latter is incremented so that the new top is the next
	 * vacant position in the array.
	 * @param element Element to be added to stack
	 */
	Stack.prototype.push = function(element) {
		this.datastore[this.top++] = element;
	}

	/**
	 * Pop element off the stack. This method is essentially the reverse of the push() function
	 * returning the element in the top position of the stack and decrementing the top variable.
	 * If a caller attempts to pop elements off an empty stack the stack is cleared (reset).
	 * @return {*} Return the element at the top of the stack, or -1 if the stack is empty.
	 */
	Stack.prototype.pop = function() {
		if(this.top <= 0) {
			this.clear(); // reinitialise the underlying datastore and reset the top variable
			return -1;	  // return -1 to indicate to the caller that the operation was unsuccessful
		} else {
			this.datastore.pop(); // use Array.prototype.pop to remove last element of datastore
			return this.datastore[--this.top];
		}
	}

	/**
	 * Return the top element of the stack by accessing the element at the top-1 position of the
	 * underlying datastore (array).
	 * @return {*} Return the top element of the stack or undefined if the stack is empty
	 */
	Stack.prototype.peek = function() {
		return this.datastore[this.top-1];
	}

	/**
	 * Reset the stack and reinitialise the underlying datastore.
	 */
	Stack.prototype.clear = function() {
		this.datastore = [];
		this.top = 0;
	}

	/**
	 * Get the number of elements in the stack.
	 * @return {number} Return the number of elements in the stack.
	 */
	Stack.prototype.getLength = function() {
		return this.top;
	};

	/**
	 * Get a string representation of the stack.
	 * @return {string} Return a string representation of the stack.
	 */
	Stack.prototype.toString = function() {
		return this.datastore.toString();
	}

	return Stack;
}());<|endoftext|>


<commit_before>import { FS } from 'grind-support'
import { encodeHTML } from 'entities'

const acorn = require('acorn')
acorn.walk = require('acorn/dist/walk').simple

const astring = require('astring')

export class Stone {

	context = {
		escape: encodeHTML
	}

	async compile(template) {
		let contents = (await FS.readFile(template)).toString().trim()

		const expressions = [ ]
		let match = null

		while((match = contents.match(/@([a-z0-9_]+)(\s*\()?/))) {
			if(match.index > 0) {
				expressions.push({
					type: 'string',
					contents: contents.substring(0, match.index)
				})

				contents = contents.substring(match.index)
			}

			let args = null

			if(match[2]) {
				let openCount = -1
				let index = 0
				let startIndex = 0
				let lastIndex = 0

				while(openCount !== 0 && (index = indexOfParenthesis(contents, index)) >= 0) {
					const parenthesis = contents.substring(index, index + 1)

					if(parenthesis === ')') {
						openCount--
					} else if(openCount === -1) {
						startIndex = index
						openCount = 1
					} else {
						openCount++
					}

					lastIndex = index
					index++
				}

				args = contents.substring(startIndex + 1, lastIndex)
				contents = contents.substring(lastIndex + 1)
			} else {
				contents = contents.substring(match[0].length)
			}

			const method = `compile${match[1][0].toUpperCase()}${match[1].substring(1)}`
			const result = this[method](args)

			if(!result.isNil) {
				expressions.push({
					type: 'code',
					contents: result
				})
			}
		}

		contents = contents.trim()

		if(contents.length > 0) {
			expressions.push({
				type: 'string',
				contents: contents
			})
		}

		let code = ''

		for(const { type, contents } of expressions) {
			if(type === 'code') {
				code += `${contents}\n`
			} else {
				code += `output += \`${sanetizeString(contents)}\`;\n`
			}
		}

		code = `template = function(_) {\nlet output = '';\n${this._contextualize(code)}\nreturn output;\n}`

		return eval(code)
	}

	render(template, context) {
		return this.compile(template).then(template => template({ ...this.context, ...context }))
	}

	_contextualize(code) {
		const tree = acorn.parse(code)
		const scopes = [
			{
				locals: new Set,
				end: Number.MAX_VALUE
			}
		]

		let scope = scopes[0]

		const checkScope = fromNode => {
			while(fromNode.start >= scope.end && scopes.length > 1) {
				scopes.pop()
				scope = scopes[scopes.length - 1]
			}
		}

		acorn.walk(tree, {
			Statement: node => {
				checkScope(node)
			},

			BlockStatement: node => {
				checkScope(node)

				scope = {
					locals: new Set(scope.locals),
					node: node,
					end: node.end
				}

				scopes.push(scope)
			},

			VariableDeclarator: node => {
				checkScope(node)
				scope.locals.add(node.id.name)
			},

			Identifier: node => {
				checkScope(node)

				if(scope.locals.has(node.name)) {
					return
				}

				node.name = `_.${node.name}`
			}
		})

		return astring(tree)
	}

	compileIf(args) {
		return `if(${args}){`
	}

	compileElseif(args) {
		return `} else if(${args}){`
	}

	compileEndif() {
		return '}'
	}

}

/**
 * [indexOfParenthesis description]
 * @param  {string} string    [description]
 * @param  {number} fromIndex [description]
 * @return {number}           [description]
 */
function indexOfParenthesis(string, fromIndex) {
	const open = string.indexOf('(', fromIndex)
	const close = string.indexOf(')', fromIndex)

	if(open === -1) {
		return close
	} else if(close === -1)  {
		return open
	}

	return Math.min(open, close)
}

function sanetizeString(string) {
	string = string.replace(/\{\{\s*(.+?)\s*\}\}/g, '${escape($1)}')
	string = string.replace(/\{!!\s*(.+?)\s*!!\}/g, '${$1}')
	string = string.replace(/[\n]/g, '\\n')
	string = string.replace(/[\r]/g, '\\r')
	string = string.replace(/[\t]/g, '\\t')

	return string
}
<commit_msg>Fixed escape to handle numbers and other non-strings<commit_after>import { FS } from 'grind-support'
import { encodeHTML } from 'entities'

const acorn = require('acorn')
acorn.walk = require('acorn/dist/walk').simple

const astring = require('astring')

export class Stone {

	context = {
		escape: value => {
			if(value.isNil) {
				return ''
			}

			return encodeHTML(value.toString())
		}

	}

	async compile(template) {
		let contents = (await FS.readFile(template)).toString().trim()

		const expressions = [ ]
		let match = null

		while((match = contents.match(/@([a-z0-9_]+)(\s*\()?/))) {
			if(match.index > 0) {
				expressions.push({
					type: 'string',
					contents: contents.substring(0, match.index)
				})

				contents = contents.substring(match.index)
			}

			let args = null

			if(match[2]) {
				let openCount = -1
				let index = 0
				let startIndex = 0
				let lastIndex = 0

				while(openCount !== 0 && (index = indexOfParenthesis(contents, index)) >= 0) {
					const parenthesis = contents.substring(index, index + 1)

					if(parenthesis === ')') {
						openCount--
					} else if(openCount === -1) {
						startIndex = index
						openCount = 1
					} else {
						openCount++
					}

					lastIndex = index
					index++
				}

				args = contents.substring(startIndex + 1, lastIndex)
				contents = contents.substring(lastIndex + 1)
			} else {
				contents = contents.substring(match[0].length)
			}

			const method = `compile${match[1][0].toUpperCase()}${match[1].substring(1)}`
			const result = this[method](args)

			if(!result.isNil) {
				expressions.push({
					type: 'code',
					contents: result
				})
			}
		}

		contents = contents.trim()

		if(contents.length > 0) {
			expressions.push({
				type: 'string',
				contents: contents
			})
		}

		let code = ''

		for(const { type, contents } of expressions) {
			if(type === 'code') {
				code += `${contents}\n`
			} else {
				code += `output += \`${sanetizeString(contents)}\`;\n`
			}
		}

		code = `template = function(_) {\nlet output = '';\n${this._contextualize(code)}\nreturn output;\n}`

		return eval(code)
	}

	render(template, context) {
		return this.compile(template).then(template => template({ ...this.context, ...context }))
	}

	_contextualize(code) {
		const tree = acorn.parse(code)
		const scopes = [
			{
				locals: new Set,
				end: Number.MAX_VALUE
			}
		]

		let scope = scopes[0]

		const checkScope = fromNode => {
			while(fromNode.start >= scope.end && scopes.length > 1) {
				scopes.pop()
				scope = scopes[scopes.length - 1]
			}
		}

		acorn.walk(tree, {
			Statement: node => {
				checkScope(node)
			},

			BlockStatement: node => {
				checkScope(node)

				scope = {
					locals: new Set(scope.locals),
					node: node,
					end: node.end
				}

				scopes.push(scope)
			},

			VariableDeclarator: node => {
				checkScope(node)
				scope.locals.add(node.id.name)
			},

			Identifier: node => {
				checkScope(node)

				if(scope.locals.has(node.name)) {
					return
				}

				node.name = `_.${node.name}`
			}
		})

		return astring(tree)
	}

	compileIf(args) {
		return `if(${args}){`
	}

	compileElseif(args) {
		return `} else if(${args}){`
	}

	compileEndif() {
		return '}'
	}

}

/**
 * [indexOfParenthesis description]
 * @param  {string} string    [description]
 * @param  {number} fromIndex [description]
 * @return {number}           [description]
 */
function indexOfParenthesis(string, fromIndex) {
	const open = string.indexOf('(', fromIndex)
	const close = string.indexOf(')', fromIndex)

	if(open === -1) {
		return close
	} else if(close === -1)  {
		return open
	}

	return Math.min(open, close)
}

function sanetizeString(string) {
	string = string.replace(/\{\{\s*(.+?)\s*\}\}/g, '${escape($1)}')
	string = string.replace(/\{!!\s*(.+?)\s*!!\}/g, '${$1}')
	string = string.replace(/[\n]/g, '\\n')
	string = string.replace(/[\r]/g, '\\r')
	string = string.replace(/[\t]/g, '\\t')

	return string
}
<|endoftext|>