<commit_before>const server = 'ws://127.0.0.1:3000';

describe('JS-Nexus', function() {
  setSpecHelper(NexusSpecHelpers);
  manageWebSockets();

  describe('user:', function() {
    let user;
    beforeEach(async function(){
      user = await Nexus(server);
    });

    describe('getHosts()', function() {
      it('only returns active hosts', async function() {
        const host1 = await Nexus(server).host('Pac-Man');
        const host2 = await Nexus(server).host('Donkey Kong');

        let list = await user.getHosts();
        this.expectHostToBeListed(host1, list);
        this.expectHostToBeListed(host2, list);

        await host1.close();
        list = await user.getHosts();
        this.expectHostNotToBeListed(host1, list);
        this.expectHostToBeListed(host2, list);

        await host2.close();
        list = await user.getHosts();
        this.expectHostNotToBeListed(host1, list);
        this.expectHostNotToBeListed(host2, list);
      });
    });

    when('the server is down', function() {
      const badServer = 'ws://127.0.0.1:777';

      it('triggers a .catch', async function() {
        let caught = false;
        await Nexus(badServer).catch(() => caught = true);
        expect(caught).toBe(true);
      });

      it('triggers the second parameter of a .then', async function() {
        let resolved = false;
        let caught = false;

        await Nexus(badServer).then(
          () => resolved = true,
          () => caught = true,
        );
        expect(resolved).toBe(false);
        expect(caught).toBe(true);
      });

      it('triggers a .catch even if they tried to .join', async function() {
        let caught = null;

        await Nexus(badServer)
          .join('The Game')
          .catch(() => caught = true);

        expect(caught).toBe(true);
      });

      it('triggers a .catch even if they tried to .host', async function() {
        let caught = null;

        await Nexus(badServer)
          .host('The Game')
          .catch(() => caught = true);

        expect(caught).toBe(true);
      });
    });
  });

  describe('client:', function() {
    let client;
    beforeEach(async function(){
      client = await timebox(Nexus(server));
    });

    it('can connect to a host by name', async function() {
      const name = 'Frogger';
      const host = await timebox(Nexus(server).host(name));

      const onNewClient = host.onNewClient.then((id, request) => {
        expect(id).toBe(1);
        expect(request).toEqual({
          type: 'CONNECT',
          hostName: name,
        });
      });
      await Promise.all([
        client.join(name),
        onNewClient,
      ]);
    });

    it('can connect to a host by id', async function() {
      const host = await timebox(Nexus(server).host("Asteroids"));

      const onNewClient = host.onNewClient.then((id, request) => {
        expect(id).toBe(1);
        expect(request).toEqual({
          type: 'CONNECT',
          hostID: host.id,
        });
      });
      await Promise.all([
        client.join(host.id),
        onNewClient,
      ]);
    });

    when('connected to a host', function() {
      beforeEach(async function() {
        this.host = await Nexus(server).host('Space Invaders');

        client.join(this.host.id);
        await this.host.onNewClient;
        await client.joined;
      });

      it('can send messages to the host', async function() {
        const msg = "Hello there!";
        client.send(msg);

        await this.host.onMessage.then((message, id) => {
          expect(message).toBe(msg);
          expect(id).toBe(1);
        });
      });

      it('can receive messages from the host', async function() {
        const msg = "General Kenobi";
        this.host.send(msg);

        const message = await client.onMessage;
        expect(message).toBe(msg);
      });
    });

    when('attempting to connect to a non-existent host', function() {
      it('triggers a .catch', async function() {
        let caught = null;
        client.join({ hostID: -18237867 });
        await client.catch(() => caught = true);

        expect(caught).toBe(true);
      });

      it('triggers the second parameter of a .then', async function() {
        let caught = null;
        client.join({ hostID: -18237867 });
        await client.then(
          () => 'should not call this',
          () => caught = true,
        );
        expect(caught).toBe(true);
      });
    });
  });

  describe('a host', function() {
    let host;

    beforeEach(async function() {
      host = await Nexus(server).host('Tron');
    });

    when('it has clients', function() {
      let client1;
      let client2;
      let client3;

      beforeEach(async function() {
        client1 = await Nexus(server).join(host.id);
        client2 = await Nexus(server).join(host.id);
        client3 = await Nexus(server).join(host.id);
      });

      it('can message exactly one of them at a time', async function() {
        let msg1=null;
        let msg2=null;
        let msg3=null;

        client1.onMessage(m => msg1=m);
        client2.onMessage(m => msg2=m);
        client3.onMessage(m => msg3=m);

        host.send('one', 1);

        await client1.onMessage;
        expect(msg1).toEqual('one');
        expect(msg2).not.toExist();
        expect(msg3).not.toExist();

        host.send('two', 2);

        await client2.onMessage;
        expect(msg1).toEqual('one');
        expect(msg2).toEqual('two');
        expect(msg3).not.toExist();

        host.send('three', 3);

        await client3.onMessage;
        expect(msg1).toEqual('one');
        expect(msg2).toEqual('two');
        expect(msg3).toEqual('three');
      });

      it('does not let a message from one client leak to others', async function() {
        let msg1=null;
        let msg2=null;
        let msg3=null;

        client1.onMessage(m => msg1=m);
        client2.onMessage(m => msg2=m);
        client3.onMessage(m => msg3=m);

        function verifySecret(msg, id) {
          expect(msg).toEqual('secret');
          expect(id).toEqual(2);
        }

        client2.send('secret');
        await host.onMessage.then(verifySecret);

        client2.send('another secret');
        const msg = await host.onMessage;
        expect(msg).toBe('another secret');

        expect(msg1).toBe(null);
        expect(msg2).toBe(null);
        expect(msg3).toBe(null);
      });
    });
  });
});
<commit_msg>Ensure host can message multiple specific clients<commit_after>const server = 'ws://127.0.0.1:3000';

describe('JS-Nexus', function() {
  setSpecHelper(NexusSpecHelpers);
  manageWebSockets();

  describe('user:', function() {
    let user;
    beforeEach(async function(){
      user = await Nexus(server);
    });

    describe('getHosts()', function() {
      it('only returns active hosts', async function() {
        const host1 = await Nexus(server).host('Pac-Man');
        const host2 = await Nexus(server).host('Donkey Kong');

        let list = await user.getHosts();
        this.expectHostToBeListed(host1, list);
        this.expectHostToBeListed(host2, list);

        await host1.close();
        list = await user.getHosts();
        this.expectHostNotToBeListed(host1, list);
        this.expectHostToBeListed(host2, list);

        await host2.close();
        list = await user.getHosts();
        this.expectHostNotToBeListed(host1, list);
        this.expectHostNotToBeListed(host2, list);
      });
    });

    when('the server is down', function() {
      const badServer = 'ws://127.0.0.1:777';

      it('triggers a .catch', async function() {
        let caught = false;
        await Nexus(badServer).catch(() => caught = true);
        expect(caught).toBe(true);
      });

      it('triggers the second parameter of a .then', async function() {
        let resolved = false;
        let caught = false;

        await Nexus(badServer).then(
          () => resolved = true,
          () => caught = true,
        );
        expect(resolved).toBe(false);
        expect(caught).toBe(true);
      });

      it('triggers a .catch even if they tried to .join', async function() {
        let caught = null;

        await Nexus(badServer)
          .join('The Game')
          .catch(() => caught = true);

        expect(caught).toBe(true);
      });

      it('triggers a .catch even if they tried to .host', async function() {
        let caught = null;

        await Nexus(badServer)
          .host('The Game')
          .catch(() => caught = true);

        expect(caught).toBe(true);
      });
    });
  });

  describe('client:', function() {
    let client;
    beforeEach(async function(){
      client = await timebox(Nexus(server));
    });

    it('can connect to a host by name', async function() {
      const name = 'Frogger';
      const host = await timebox(Nexus(server).host(name));

      const onNewClient = host.onNewClient.then((id, request) => {
        expect(id).toBe(1);
        expect(request).toEqual({
          type: 'CONNECT',
          hostName: name,
        });
      });
      await Promise.all([
        client.join(name),
        onNewClient,
      ]);
    });

    it('can connect to a host by id', async function() {
      const host = await timebox(Nexus(server).host("Asteroids"));

      const onNewClient = host.onNewClient.then((id, request) => {
        expect(id).toBe(1);
        expect(request).toEqual({
          type: 'CONNECT',
          hostID: host.id,
        });
      });
      await Promise.all([
        client.join(host.id),
        onNewClient,
      ]);
    });

    when('connected to a host', function() {
      beforeEach(async function() {
        this.host = await Nexus(server).host('Space Invaders');

        client.join(this.host.id);
        await this.host.onNewClient;
        await client.joined;
      });

      it('can send messages to the host', async function() {
        const msg = "Hello there!";
        client.send(msg);

        await this.host.onMessage.then((message, id) => {
          expect(message).toBe(msg);
          expect(id).toBe(1);
        });
      });

      it('can receive messages from the host', async function() {
        const msg = "General Kenobi";
        this.host.send(msg);

        const message = await client.onMessage;
        expect(message).toBe(msg);
      });
    });

    when('attempting to connect to a non-existent host', function() {
      it('triggers a .catch', async function() {
        let caught = null;
        client.join({ hostID: -18237867 });
        await client.catch(() => caught = true);

        expect(caught).toBe(true);
      });

      it('triggers the second parameter of a .then', async function() {
        let caught = null;
        client.join({ hostID: -18237867 });
        await client.then(
          () => 'should not call this',
          () => caught = true,
        );
        expect(caught).toBe(true);
      });
    });
  });

  describe('a host', function() {
    let host;

    beforeEach(async function() {
      host = await Nexus(server).host('Tron');
    });

    when('it has clients', function() {
      let client1;
      let client2;
      let client3;

      beforeEach(async function() {
        client1 = await Nexus(server).join(host.id);
        client2 = await Nexus(server).join(host.id);
        client3 = await Nexus(server).join(host.id);
      });

      it('can message exactly one of them at a time', async function() {
        let msg1=null;
        let msg2=null;
        let msg3=null;

        client1.onMessage(m => msg1=m);
        client2.onMessage(m => msg2=m);
        client3.onMessage(m => msg3=m);

        host.send('one', 1);

        await client1.onMessage;
        expect(msg1).toEqual('one');
        expect(msg2).not.toExist();
        expect(msg3).not.toExist();

        host.send('two', 2);

        await client2.onMessage;
        expect(msg1).toEqual('one');
        expect(msg2).toEqual('two');
        expect(msg3).not.toExist();

        host.send('three', 3);

        await client3.onMessage;
        expect(msg1).toEqual('one');
        expect(msg2).toEqual('two');
        expect(msg3).toEqual('three');
      });

      it('does not let a message from one client leak to others', async function() {
        let msg1=null;
        let msg2=null;
        let msg3=null;

        client1.onMessage(m => msg1=m);
        client2.onMessage(m => msg2=m);
        client3.onMessage(m => msg3=m);

        function verifySecret(msg, id) {
          expect(msg).toEqual('secret');
          expect(id).toEqual(2);
        }

        client2.send('secret');
        await host.onMessage.then(verifySecret);

        client2.send('another secret');
        const msg = await host.onMessage;
        expect(msg).toBe('another secret');

        expect(msg1).toBe(null);
        expect(msg2).toBe(null);
        expect(msg3).toBe(null);
      });

      it('can message multiple clients', async function() {
        let msg1=null;
        let msg2=null;
        let msg3=null;

        client1.onMessage(m => msg1=m);
        client2.onMessage(m => msg2=m);
        client3.onMessage(m => msg3=m);

        const msgOneTwo = 'one and two';
        const msgTwoThree = 'two and three';

        host.send(msgOneTwo, [1, 2]);
        await Promise.all([
          client1.onMessage,
          client2.onMessage,
        ]);

        expect(msg1).toBe(msgOneTwo);
        expect(msg2).toBe(msgOneTwo);
        expect(msg3).toBe(null);

        host.send(msgTwoThree, [2, 3]);
        await Promise.all([
          client2.onMessage,
          client3.onMessage,
        ]);

        expect(msg1).toBe(msgOneTwo);
        expect(msg2).toBe(msgTwoThree);
        expect(msg3).toBe(msgTwoThree);
      });
    });
  });
});
<|endoftext|>


<commit_before>'use strict';

/**
 * Module dependencies.
 */
var utils = require('../../../../common/utils'),
    http = require('http'),
    https = require('https'),
    querystring = require('querystring'),
    Abstract = require('../../../../common/http/event/notifier/request')
;

/**
 * Expose `Request`.
 */
module.exports = Request;

/**
 * Initialize a new request notifier.
 */
function Request() {
    Abstract.call(this);
}

utils.extend(Abstract, Request);

Request.defineDependency('_app', 'function');
Request.defineDependency('_escaper', 'danf:manipulation.escaper');
Request.defineDependency('_renderer', 'danf:rendering.renderer');
Request.defineDependency('_errorHandler', 'danf:http.errorHandler');
Request.defineDependency('_logger', 'danf:logging.logger');

/**
 * @interface {danf:event.notifier}
 */
Object.defineProperty(Request.prototype, 'name', {
    value: 'request'
});

/**
 * App.
 *
 * @var {function}
 * @api public
 */
Object.defineProperty(Request.prototype, 'app', {
    set: function(app) { this._app = app; }
});

/**
 * Escaper.
 *
 * @var {danf:manipulation.escaper}
 * @api public
 */
Object.defineProperty(Request.prototype, 'escaper', {
    set: function(escaper) { this._escaper = escaper; }
});

/**
 * Renderer.
 *
 * @var {danf:rendering.renderer}
 * @api public
 */
Object.defineProperty(Request.prototype, 'renderer', {
    set: function(renderer) { this._renderer = renderer; }
});

/**
 * Error handler.
 *
 * @var {danf:http.errorHandler}
 * @api public
 */
Object.defineProperty(Request.prototype, 'errorHandler', {
    set: function(errorHandler) { this._errorHandler = errorHandler; }
});

/**
 * Logger.
 *
 * @var {danf:logging.logger}
 * @api public
 */
Object.defineProperty(Request.prototype, 'logger', {
    set: function(logger) { this._logger = logger; }
});

/**
 * @inheritdoc
 */
Request.prototype.getSpecificContract = function() {
    return {
        view: this._renderer.contract
    };
}

/**
 * @inheritdoc
 */
Request.prototype.addEventListener = function(name, parameters, sequence) {
    var self = this,
        path = parameters.path ? parameters.path : '/',
        headers = parameters.headers,
        methods = parameters.methods,
        params = parameters.parameters,
        view = parameters.view
    ;

    for (var i = 0; i < methods.length; i++) {
        (function(method) {
            self._app[method](path, function(request, response, next) {
                var stream = utils.merge(request.query, request.params),
                    errored = false,
                    escapedReferences = ['@', '$', '&', '~', '!'],
                    socketId = request.session.socket || 0,
                    socket = self._app.io.sockets.connected[socketId]
                ;

                // Synchronize socket session.
                if (socket) {
                    socket.request.session = request.session;
                }

                // Escape stream to prevent parameters injection.
                stream = self._escaper.escape(stream, escapedReferences);

                try {
                    if (params) {
                        try {
                            stream = self._dataResolver.resolve(
                                stream,
                                params,
                                'request[{0}]'.format(name)
                            );
                        } catch (error) {
                            error.status = 400;

                            throw error;
                        }
                    }

                    sequence.execute(
                        stream,
                        {
                            request: request,
                            response: response
                        },
                        '.',
                        function(errors, stream) {
                            var error = new Error(
                                    'Request "{0}" sequence processing failed.'.format(name)
                                )
                            ;

                            errored = true;
                            error.embedded = errors;

                            handleRequestError.call(self, response, error);
                        },
                        function(stream) {
                            try {
                                if (!errored) {
                                    stream._context = JSON.stringify(self._app.clientContext);

                                    self._renderer.render(request, response, stream, view, function(content) {
                                        for (var headerName in headers) {
                                            response.set(headerName, headers[headerName]);
                                        }

                                        // Synchronize socket session.
                                        if (socket) {
                                            socket.request.session = request.session;
                                        }

                                        // Process double unescaping.
                                        content = self._escaper.unescape(content, escapedReferences);
                                        content = self._escaper.unescape(content, escapedReferences);

                                        if (null == response.statusCode || 200 === response.statusCode) {
                                            if ('post' === method) {
                                                response.statusCode = 201;
                                            } else if (null == content || '' === content) {
                                                response.statusCode = 204;
                                            } else {
                                                response.statusCode = 200;
                                            }
                                        }

                                        if (response.statusCode < 300) {
                                            response.set('X-Danf-Path', request.originalUrl);
                                            response.send(content);
                                        } else {
                                            var error = new Error();

                                            error.status = response.statusCode;
                                            error.message = content.error ? content.error : '';

                                            throw error;
                                        }
                                    });
                                }
                            } catch (error) {
                                handleRequestError.call(self, response, error);
                            }
                        }
                    );
                } catch (error) {
                    handleRequestError.call(self, response, error);
                }
            });
        })(methods[i].toLowerCase());
    }
}

/**
 * @inheritdoc
 */
Request.prototype.notifyEvent = function(name, parameters, sequence, data, meta) {
    var callback = data.callback,
        parameters = 'object' === typeof data.parameters
            ? querystring.stringify(data.parameters)
            : data.parameters
        ,
        path = data.path,
        body = ''
    ;

    if ('GET' === data.method && '' !== parameters) {
        path = '{0}{1}{2}'.format(
            path,
            -1 === path.indexOf('?') ? '?' : '&',
            parameters
        );
    } else {
        body = parameters;
    }

    var options = {
            path: path,
            method: data.method,
            port: data.port ? data.port : this._app.context.port,
            protocol: data.protocol,
            headers: data.headers
        },
        requestCallback
    ;

    if (data.hostname) {
        options.hostname = data.hostname;
    }

    this.executeRequest(options, body);
}

/**
 * Execute a request.
 *
 * @param {object} options The metadatas of the request.
 * @param {string} body The data of the request.
 * @api protected
 */
Request.prototype.executeRequest = function(options, body) {
    this.__asyncProcess(function(async) {
        var protocol = /^https:/i.test(options.protocol)
                ? https
                : http
            ,
            request = protocol.request(options, function(response) {
                var content = ''

                response.on('data', function(chunk) {
                    content += chunk;
                });

                response.on('end', async(function() {
                    return content;
                }));
            })
        ;

        if ('' !== body) {
            request.write(body);
        }

        request.end();
    });
}

/**
 * Handle a request error and redirection.
 *
 * @param {object} response The express response object.
 * @param {error} error The error.
 * @api private
 */
var handleRequestError = function(response, error) {
    error = this._errorHandler.process(error);
    response.statusCode = error.status;

    // Handle redirect.
    if (response.statusCode < 400) {
        response.redirect(response.statusCode, error.message);
    // Handle error.
    } else {
        this._logger.log('<<error>>{0}'.format(error.message), 1);
        this._logger.log(
            '<<grey>>{0}'.format(
                error.stack.replace(error.message, '')
            ),
            2,
            1,
            0
        );

        if (error.name === error.message) {
            response.send('{0}'.format(error.message));
        } else {
            response.send('{0}: {1}'.format(error.name, error.message));
        }
    }
}<commit_msg>Fix optional sockets handling<commit_after>'use strict';

/**
 * Module dependencies.
 */
var utils = require('../../../../common/utils'),
    http = require('http'),
    https = require('https'),
    querystring = require('querystring'),
    Abstract = require('../../../../common/http/event/notifier/request')
;

/**
 * Expose `Request`.
 */
module.exports = Request;

/**
 * Initialize a new request notifier.
 */
function Request() {
    Abstract.call(this);
}

utils.extend(Abstract, Request);

Request.defineDependency('_app', 'function');
Request.defineDependency('_escaper', 'danf:manipulation.escaper');
Request.defineDependency('_renderer', 'danf:rendering.renderer');
Request.defineDependency('_errorHandler', 'danf:http.errorHandler');
Request.defineDependency('_logger', 'danf:logging.logger');

/**
 * @interface {danf:event.notifier}
 */
Object.defineProperty(Request.prototype, 'name', {
    value: 'request'
});

/**
 * App.
 *
 * @var {function}
 * @api public
 */
Object.defineProperty(Request.prototype, 'app', {
    set: function(app) { this._app = app; }
});

/**
 * Escaper.
 *
 * @var {danf:manipulation.escaper}
 * @api public
 */
Object.defineProperty(Request.prototype, 'escaper', {
    set: function(escaper) { this._escaper = escaper; }
});

/**
 * Renderer.
 *
 * @var {danf:rendering.renderer}
 * @api public
 */
Object.defineProperty(Request.prototype, 'renderer', {
    set: function(renderer) { this._renderer = renderer; }
});

/**
 * Error handler.
 *
 * @var {danf:http.errorHandler}
 * @api public
 */
Object.defineProperty(Request.prototype, 'errorHandler', {
    set: function(errorHandler) { this._errorHandler = errorHandler; }
});

/**
 * Logger.
 *
 * @var {danf:logging.logger}
 * @api public
 */
Object.defineProperty(Request.prototype, 'logger', {
    set: function(logger) { this._logger = logger; }
});

/**
 * @inheritdoc
 */
Request.prototype.getSpecificContract = function() {
    return {
        view: this._renderer.contract
    };
}

/**
 * @inheritdoc
 */
Request.prototype.addEventListener = function(name, parameters, sequence) {
    var self = this,
        path = parameters.path ? parameters.path : '/',
        headers = parameters.headers,
        methods = parameters.methods,
        params = parameters.parameters,
        view = parameters.view
    ;

    for (var i = 0; i < methods.length; i++) {
        (function(method) {
            self._app[method](path, function(request, response, next) {
                var stream = utils.merge(request.query, request.params),
                    errored = false,
                    escapedReferences = ['@', '$', '&', '~', '!'],
                    socketId = request.session.socket || 0,
                    socket = self._app.io
                        ? self._app.io.sockets.connected[socketId]
                        : null
                ;

                // Synchronize socket session.
                if (socket) {
                    socket.request.session = request.session;
                }

                // Escape stream to prevent parameters injection.
                stream = self._escaper.escape(stream, escapedReferences);

                try {
                    if (params) {
                        try {
                            stream = self._dataResolver.resolve(
                                stream,
                                params,
                                'request[{0}]'.format(name)
                            );
                        } catch (error) {
                            error.status = 400;

                            throw error;
                        }
                    }

                    sequence.execute(
                        stream,
                        {
                            request: request,
                            response: response
                        },
                        '.',
                        function(errors, stream) {
                            var error = new Error(
                                    'Request "{0}" sequence processing failed.'.format(name)
                                )
                            ;

                            errored = true;
                            error.embedded = errors;

                            handleRequestError.call(self, response, error);
                        },
                        function(stream) {
                            try {
                                if (!errored) {
                                    stream._context = JSON.stringify(self._app.clientContext);

                                    self._renderer.render(request, response, stream, view, function(content) {
                                        for (var headerName in headers) {
                                            response.set(headerName, headers[headerName]);
                                        }

                                        // Synchronize socket session.
                                        if (socket) {
                                            socket.request.session = request.session;
                                        }

                                        // Process double unescaping.
                                        content = self._escaper.unescape(content, escapedReferences);
                                        content = self._escaper.unescape(content, escapedReferences);

                                        if (null == response.statusCode || 200 === response.statusCode) {
                                            if ('post' === method) {
                                                response.statusCode = 201;
                                            } else if (null == content || '' === content) {
                                                response.statusCode = 204;
                                            } else {
                                                response.statusCode = 200;
                                            }
                                        }

                                        if (response.statusCode < 300) {
                                            response.set('X-Danf-Path', request.originalUrl);
                                            response.send(content);
                                        } else {
                                            var error = new Error();

                                            error.status = response.statusCode;
                                            error.message = content.error ? content.error : '';

                                            throw error;
                                        }
                                    });
                                }
                            } catch (error) {
                                handleRequestError.call(self, response, error);
                            }
                        }
                    );
                } catch (error) {
                    handleRequestError.call(self, response, error);
                }
            });
        })(methods[i].toLowerCase());
    }
}

/**
 * @inheritdoc
 */
Request.prototype.notifyEvent = function(name, parameters, sequence, data, meta) {
    var callback = data.callback,
        parameters = 'object' === typeof data.parameters
            ? querystring.stringify(data.parameters)
            : data.parameters
        ,
        path = data.path,
        body = ''
    ;

    if ('GET' === data.method && '' !== parameters) {
        path = '{0}{1}{2}'.format(
            path,
            -1 === path.indexOf('?') ? '?' : '&',
            parameters
        );
    } else {
        body = parameters;
    }

    var options = {
            path: path,
            method: data.method,
            port: data.port ? data.port : this._app.context.port,
            protocol: data.protocol,
            headers: data.headers
        },
        requestCallback
    ;

    if (data.hostname) {
        options.hostname = data.hostname;
    }

    this.executeRequest(options, body);
}

/**
 * Execute a request.
 *
 * @param {object} options The metadatas of the request.
 * @param {string} body The data of the request.
 * @api protected
 */
Request.prototype.executeRequest = function(options, body) {
    this.__asyncProcess(function(async) {
        var protocol = /^https:/i.test(options.protocol)
                ? https
                : http
            ,
            request = protocol.request(options, function(response) {
                var content = ''

                response.on('data', function(chunk) {
                    content += chunk;
                });

                response.on('end', async(function() {
                    return content;
                }));
            })
        ;

        if ('' !== body) {
            request.write(body);
        }

        request.end();
    });
}

/**
 * Handle a request error and redirection.
 *
 * @param {object} response The express response object.
 * @param {error} error The error.
 * @api private
 */
var handleRequestError = function(response, error) {
    error = this._errorHandler.process(error);
    response.statusCode = error.status;

    // Handle redirect.
    if (response.statusCode < 400) {
        response.redirect(response.statusCode, error.message);
    // Handle error.
    } else {
        this._logger.log('<<error>>{0}'.format(error.message), 1);
        this._logger.log(
            '<<grey>>{0}'.format(
                error.stack.replace(error.message, '')
            ),
            2,
            1,
            0
        );

        if (error.name === error.message) {
            response.send('{0}'.format(error.message));
        } else {
            response.send('{0}: {1}'.format(error.name, error.message));
        }
    }
}<|endoftext|>


<commit_before>/**
* Copyright (c) Microsoft.  All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*   http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

// Module dependencies.
var http = require('http');
var url = require('url');
var util = require('util');
var qs = require('qs');
var crypto = require('crypto');
var xml2js = require('xml2js');

var azureutil = require('../../util/util');

var ServiceClient = require('./serviceclient');
var Constants = require('../../util/constants');
var HeaderConstants = Constants.HeaderConstants;
var QueryStringConstants = Constants.QueryStringConstants;
var HttpConstants = Constants.HttpConstants;
var Logger = require('../../diagnostics/logger');

// Expose 'StorageServiceClient'.
exports = module.exports = StorageServiceClient;

// Validation error messages
StorageServiceClient.incorrectStorageAccountErr = 'Account name must be a non empty string. Set AZURE_STORAGE_ACCOUNT';
StorageServiceClient.incorrectStorageAccessKeyErr = 'AccessKey must be a non empty string. Set AZURE_STORAGE_ACCESS_KEY';

/**
* Creates a new ServiceClient object.
*
* @constructor
* @param {string} storageAccountOrConnectionString    The storage account or connection string.
* @param {string} storageAccessKey                    The storage access key.
* @param {string} host                                The host for the service.
* @param {object} authenticationProvider              The authentication provider object (e.g. sharedkey / sharedkeytable / sharedaccesssignature).
*/
function StorageServiceClient(storageAccountOrConnectionString, storageAccessKey, host, authenticationProvider) {
  this._setAccountCredentials(storageAccount, storageAccessKey);
  this.apiVersion = HeaderConstants.TARGET_STORAGE_VERSION;
  this.usePathStyleUri = ServiceClient.isEmulated(host);

  StorageServiceClient.super_.call(this, host, authenticationProvider);

  this._initDefaultFilter();
}

util.inherits(StorageServiceClient, ServiceClient);

/**
* Builds the request options to be passed to the http.request method.
*
* @param {WebResource} webResource The webresource where to build the options from.
* @param {object}      options     The request options.
* @param {function(error, requestOptions)}  callback  The callback function.
* @return {undefined}
*/
StorageServiceClient.prototype._buildRequestOptions = function (webResource, options, callback) {
  var self = this;

  if (!webResource.headers || !webResource.headers[HeaderConstants.CONTENT_TYPE]) {
    webResource.addOptionalHeader(HeaderConstants.CONTENT_TYPE, '');
  }

  if (!webResource.headers || webResource.headers[HeaderConstants.CONTENT_LENGTH] === undefined) {
    webResource.addOptionalHeader(HeaderConstants.CONTENT_LENGTH, 0);
  } else if(webResource.headers && webResource.headers[HeaderConstants.CONTENT_LENGTH] === null) {
    delete webResource.headers[HeaderConstants.CONTENT_LENGTH];
  }

  webResource.addOptionalHeader(HeaderConstants.STORAGE_VERSION_HEADER, this.apiVersion);
  webResource.addOptionalHeader(HeaderConstants.DATE_HEADER, new Date().toUTCString());
  webResource.addOptionalHeader(HeaderConstants.ACCEPT_HEADER, 'application/atom+xml,application/xml');
  webResource.addOptionalHeader(HeaderConstants.ACCEPT_CHARSET_HEADER, 'UTF-8');

  webResource.addOptionalHeader(HeaderConstants.HOST_HEADER, this._getHostname() + ':' + this.port);

  if (options) {
    if (options.timeoutIntervalInMs) {
      webResource.addOptionalQueryParam(QueryStringConstants.TIMEOUT, options.timeoutIntervalInMs);
    }

    if (options.accessConditions) {
      webResource.addOptionalAccessConditionHeader(options.accessConditions);
    }

    if (options.sourceAccessConditions) {
      webResource.addOptionalSourceAccessConditionHeader(options.sourceAccessConditions);
    }
  }

  // Sets the request url in the web resource.
  this._setRequestUrl(webResource);

  // Now that the web request is finalized, sign it
  this.authenticationProvider.signRequest(webResource, function (error) {
    var requestOptions = null;

    if (!error) {
      requestOptions = {
        url: url.format({
          protocol: self._isHttps() ? 'https:' : 'http:',
          hostname: self._getHostname(),
          port: self.port,
          pathname: webResource.path + webResource.getQueryString(true)
        }),
        method: webResource.httpVerb,
        headers: webResource.headers
      };

      self._setRequestOptionsProxy(requestOptions);
    }

    callback(error, requestOptions);
  });
};

/**
* Retrieves the normalized path to be used in a request.
* This takes into consideration the usePathStyleUri object field
* which specifies if the request is against the emulator or against
* the live service. It also adds a leading "/" to the path in case
* it's not there before.
*
* @param {string} path The path to be normalized.
* @return {string} The normalized path.
*/
StorageServiceClient.prototype._getPath = function (path) {
  if (path === null || path === undefined) {
    path = '/';
  } else if (path.indexOf('/') !== 0) {
    path = '/' + path;
  }

  if (this.usePathStyleUri) {
    path = '/' + this.storageAccount + path;
  }

  return path;
};

/**
* Retrives the hostname taking into consideration the usePathStyleUri flag which indicates whether the account
* should be a prefix for it or not.
*
* @return {string} The hostname.
*/
StorageServiceClient.prototype._getHostname = function () {
  if (this.usePathStyleUri) {
    return this.host;
  }

  return this.storageAccount + '.' + this.host;
};

/**
* Sets the account credentials taking into consideration the isEmulated value and the environment variables:
* AZURE_STORAGE_ACCOUNT and AZURE_STORAGE_ACCESS_KEY.
*
* @param {string} storageAccount          The storage account.
* @param {string} storageAccessKey        The storage access key.
* @return {undefined}
*/
StorageServiceClient.prototype._setAccountCredentials = function (storageAccount, storageAccessKey) {
  if (azureutil.objectIsNull(storageAccount)) {
    if (ServiceClient.isEmulated()) {
      storageAccount = ServiceClient.DEVSTORE_STORAGE_ACCOUNT;
    } else {
      storageAccount = process.env[ServiceClient.EnvironmentVariables.AZURE_STORAGE_ACCOUNT];
    }
  }

  if (azureutil.objectIsNull(storageAccessKey)) {
    if (ServiceClient.isEmulated()) {
      storageAccessKey = ServiceClient.DEVSTORE_STORAGE_ACCESS_KEY;
    } else {
      storageAccessKey = process.env[ServiceClient.EnvironmentVariables.AZURE_STORAGE_ACCESS_KEY];
    }
  }

  if (!azureutil.objectIsString(storageAccount) || azureutil.stringIsEmpty(storageAccount)) {
    throw new Error(StorageServiceClient.incorrectStorageAccountErr);
  }

  if (!azureutil.objectIsString(storageAccessKey) || azureutil.stringIsEmpty(storageAccessKey)) {
    throw new Error(StorageServiceClient.incorrectStorageAccessKeyErr);
  }

  this.storageAccount = storageAccount;
  this.storageAccessKey = storageAccessKey;
};<commit_msg>Fixing undeclared variable.<commit_after>/**
* Copyright (c) Microsoft.  All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*   http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

// Module dependencies.
var http = require('http');
var url = require('url');
var util = require('util');
var qs = require('qs');
var crypto = require('crypto');
var xml2js = require('xml2js');

var azureutil = require('../../util/util');

var ServiceClient = require('./serviceclient');
var Constants = require('../../util/constants');
var HeaderConstants = Constants.HeaderConstants;
var QueryStringConstants = Constants.QueryStringConstants;
var HttpConstants = Constants.HttpConstants;
var Logger = require('../../diagnostics/logger');

// Expose 'StorageServiceClient'.
exports = module.exports = StorageServiceClient;

// Validation error messages
StorageServiceClient.incorrectStorageAccountErr = 'Account name must be a non empty string. Set AZURE_STORAGE_ACCOUNT';
StorageServiceClient.incorrectStorageAccessKeyErr = 'AccessKey must be a non empty string. Set AZURE_STORAGE_ACCESS_KEY';

/**
* Creates a new ServiceClient object.
*
* @constructor
* @param {string} storageAccountOrConnectionString    The storage account or connection string.
* @param {string} storageAccessKey                    The storage access key.
* @param {string} host                                The host for the service.
* @param {object} authenticationProvider              The authentication provider object (e.g. sharedkey / sharedkeytable / sharedaccesssignature).
*/
function StorageServiceClient(storageAccountOrConnectionString, storageAccessKey, host, authenticationProvider) {
  var storageAccount = storageAccountOrConnectionString;

  this._setAccountCredentials(storageAccount, storageAccessKey);
  this.apiVersion = HeaderConstants.TARGET_STORAGE_VERSION;
  this.usePathStyleUri = ServiceClient.isEmulated(host);

  StorageServiceClient.super_.call(this, host, authenticationProvider);

  this._initDefaultFilter();
}

util.inherits(StorageServiceClient, ServiceClient);

/**
* Builds the request options to be passed to the http.request method.
*
* @param {WebResource} webResource The webresource where to build the options from.
* @param {object}      options     The request options.
* @param {function(error, requestOptions)}  callback  The callback function.
* @return {undefined}
*/
StorageServiceClient.prototype._buildRequestOptions = function (webResource, options, callback) {
  var self = this;

  if (!webResource.headers || !webResource.headers[HeaderConstants.CONTENT_TYPE]) {
    webResource.addOptionalHeader(HeaderConstants.CONTENT_TYPE, '');
  }

  if (!webResource.headers || webResource.headers[HeaderConstants.CONTENT_LENGTH] === undefined) {
    webResource.addOptionalHeader(HeaderConstants.CONTENT_LENGTH, 0);
  } else if(webResource.headers && webResource.headers[HeaderConstants.CONTENT_LENGTH] === null) {
    delete webResource.headers[HeaderConstants.CONTENT_LENGTH];
  }

  webResource.addOptionalHeader(HeaderConstants.STORAGE_VERSION_HEADER, this.apiVersion);
  webResource.addOptionalHeader(HeaderConstants.DATE_HEADER, new Date().toUTCString());
  webResource.addOptionalHeader(HeaderConstants.ACCEPT_HEADER, 'application/atom+xml,application/xml');
  webResource.addOptionalHeader(HeaderConstants.ACCEPT_CHARSET_HEADER, 'UTF-8');

  webResource.addOptionalHeader(HeaderConstants.HOST_HEADER, this._getHostname() + ':' + this.port);

  if (options) {
    if (options.timeoutIntervalInMs) {
      webResource.addOptionalQueryParam(QueryStringConstants.TIMEOUT, options.timeoutIntervalInMs);
    }

    if (options.accessConditions) {
      webResource.addOptionalAccessConditionHeader(options.accessConditions);
    }

    if (options.sourceAccessConditions) {
      webResource.addOptionalSourceAccessConditionHeader(options.sourceAccessConditions);
    }
  }

  // Sets the request url in the web resource.
  this._setRequestUrl(webResource);

  // Now that the web request is finalized, sign it
  this.authenticationProvider.signRequest(webResource, function (error) {
    var requestOptions = null;

    if (!error) {
      requestOptions = {
        url: url.format({
          protocol: self._isHttps() ? 'https:' : 'http:',
          hostname: self._getHostname(),
          port: self.port,
          pathname: webResource.path + webResource.getQueryString(true)
        }),
        method: webResource.httpVerb,
        headers: webResource.headers
      };

      self._setRequestOptionsProxy(requestOptions);
    }

    callback(error, requestOptions);
  });
};

/**
* Retrieves the normalized path to be used in a request.
* This takes into consideration the usePathStyleUri object field
* which specifies if the request is against the emulator or against
* the live service. It also adds a leading "/" to the path in case
* it's not there before.
*
* @param {string} path The path to be normalized.
* @return {string} The normalized path.
*/
StorageServiceClient.prototype._getPath = function (path) {
  if (path === null || path === undefined) {
    path = '/';
  } else if (path.indexOf('/') !== 0) {
    path = '/' + path;
  }

  if (this.usePathStyleUri) {
    path = '/' + this.storageAccount + path;
  }

  return path;
};

/**
* Retrives the hostname taking into consideration the usePathStyleUri flag which indicates whether the account
* should be a prefix for it or not.
*
* @return {string} The hostname.
*/
StorageServiceClient.prototype._getHostname = function () {
  if (this.usePathStyleUri) {
    return this.host;
  }

  return this.storageAccount + '.' + this.host;
};

/**
* Sets the account credentials taking into consideration the isEmulated value and the environment variables:
* AZURE_STORAGE_ACCOUNT and AZURE_STORAGE_ACCESS_KEY.
*
* @param {string} storageAccount          The storage account.
* @param {string} storageAccessKey        The storage access key.
* @return {undefined}
*/
StorageServiceClient.prototype._setAccountCredentials = function (storageAccount, storageAccessKey) {
  if (azureutil.objectIsNull(storageAccount)) {
    if (ServiceClient.isEmulated()) {
      storageAccount = ServiceClient.DEVSTORE_STORAGE_ACCOUNT;
    } else {
      storageAccount = process.env[ServiceClient.EnvironmentVariables.AZURE_STORAGE_ACCOUNT];
    }
  }

  if (azureutil.objectIsNull(storageAccessKey)) {
    if (ServiceClient.isEmulated()) {
      storageAccessKey = ServiceClient.DEVSTORE_STORAGE_ACCESS_KEY;
    } else {
      storageAccessKey = process.env[ServiceClient.EnvironmentVariables.AZURE_STORAGE_ACCESS_KEY];
    }
  }

  if (!azureutil.objectIsString(storageAccount) || azureutil.stringIsEmpty(storageAccount)) {
    throw new Error(StorageServiceClient.incorrectStorageAccountErr);
  }

  if (!azureutil.objectIsString(storageAccessKey) || azureutil.stringIsEmpty(storageAccessKey)) {
    throw new Error(StorageServiceClient.incorrectStorageAccessKeyErr);
  }

  this.storageAccount = storageAccount;
  this.storageAccessKey = storageAccessKey;
};<|endoftext|>


<commit_before>var _ = require('lodash');
var moment = require('frozen-moment');
var periodTools = require('./periodTools');
var ressourcesMap = require('./ressources');
var individuSchema = require('./individu');
var menageSchema = require('./menage');
var familleSchema = require('./famille');
var foyerSchema = require('./foyerFiscal');
var common = require('./common');
var PRESTATIONS = require('../prestations');

var isNotValidValue = function(value) {
    return _.isNaN(value) || _.isUndefined(value) || value === null;
};

var getPeriods = function(dateDeValeur) {
    dateDeValeur = moment(dateDeValeur).freeze();
    return {
        moisCourant: {
            key: dateDeValeur.format('YYYY-MM')
        },
        moisPrecedent: {
            key: dateDeValeur.subtract(1, 'months').format('YYYY-MM')
        },
        moisMMoins2: {
            key: dateDeValeur.subtract(2, 'months').format('YYYY-MM')
        },
        moisMMoins3: {
            key: dateDeValeur.subtract(3, 'months').format('YYYY-MM')
        },
       anneeGlissante: {
            debut: dateDeValeur.subtract(1, 'years').subtract(1, 'months'),
            fin: dateDeValeur,
            key: 'year:' + dateDeValeur.subtract(1, 'years').format('YYYY-MM')
        },
        anneeFiscalReference: {
            key: dateDeValeur.subtract(2, 'years').year()
        },
        trimestreGlissant: {
            debut: dateDeValeur.subtract(3, 'months').subtract(1, 'months'),
            fin: dateDeValeur,
            key: dateDeValeur.subtract(3, 'months').format('YYYY-MM') + ':3'
        }
    };
};

var applyRessources = function(mesAidesEntity, openFiscaEntity, mappingSchema, dateDeValeur) {
    var periods = getPeriods(dateDeValeur);

    function isPeriodWithin(agregatKey, periode) {
        return periode.isAfter(periods[agregatKey].debut, 'month') && periode.isBefore(periods[agregatKey].fin, 'month');
    }

    var ressourcesByType = _.groupBy(mesAidesEntity.ressources, 'type');

    _.forEach(mappingSchema, function(definitions, openFiscaKey) {
        if (! _.isArray(definitions)) {
            definitions = [definitions];
        }

        _.forEach(definitions, function(definition) {

            var srcKey = definition.src || definition;
            var fn = definition.fn;

            if (ressourcesByType[srcKey]) {
                var result = {};
                var agr = {};

                _.forEach(ressourcesByType[srcKey], function(item) {
                    if (item.periode) {
                        // Période d'origine
                        if (!result[item.periode]) result[item.periode] = 0;
                        result[item.periode] += item.montant;

                        // Aggrégats
                        _.keys(periods).forEach(function (agregatKey) {
                            if (agregatKey != 'anneeFiscalReference' && isPeriodWithin(agregatKey, moment(item.periode, 'YYYY-MM'))) {
                                if (!(agregatKey in agr)) agr[agregatKey] = 0;
                                agr[agregatKey] += fn ? fn(item.montant) : item.montant;
                            }
                        });
                    }
                });

                _.forEach(result, function(montant, periode) {
                    // Applique la transformation
                    if (fn) montant = fn(montant);
                    result[periode] = montant;
                });

                // On prolonge la ressource perçue quand c'est possible
                if (result[periods.moisPrecedent.key] && !_.contains(mesAidesEntity.interruptedRessources, srcKey)) {
                    result[periods.moisCourant.key] = result[periods.moisPrecedent.key];
                }

                if (_.size(result) > 0) {
                    if (_.size(openFiscaEntity[openFiscaKey]) > 0) {
                        openFiscaEntity[openFiscaKey] = _.extend(openFiscaEntity[openFiscaKey],result);
                    } else {
                        openFiscaEntity[openFiscaKey] = result;
                    }
                }

                _.forEach(agr, function (value, agregatKey) {
                    openFiscaEntity[openFiscaKey][periods[agregatKey].key] = value;
                });

                // On suppose que les revenus pour l'année fiscale de référence sont les mêmes que pour les 12 derniers mois, si ces revenus n'ont pas encore
                // été renseignés
                var anneeGlissanteKey = periods.anneeGlissante.key,
                    anneeFiscaleRefKey = periods.anneeFiscalReference.key;
                if (! openFiscaEntity[openFiscaKey][anneeFiscaleRefKey]) {
                    openFiscaEntity[openFiscaKey][anneeFiscaleRefKey] = openFiscaEntity[openFiscaKey][anneeGlissanteKey];
                }
            }
        });
    });
};

var setNonInjectedPrestationsToZero = function(openFiscaEntity, dateDeValeur) {
    var periods = getPeriods(dateDeValeur);
    var concernedPeriods = ['moisPrecedent', 'moisMMoins2', 'moisMMoins3', 'anneeGlissante'];

    _.forEach(concernedPeriods, function(periodKey) {
        var period = periods[periodKey].key;
        var prestations = _.pick(PRESTATIONS, function(definition) {
            return definition.type == Number;
        });

        _.forEach(prestations, function(definition, prestationName) {
            if (! openFiscaEntity[prestationName]) {
                openFiscaEntity[prestationName] = {};
            }
            if (! openFiscaEntity[prestationName][period]) {
                openFiscaEntity[prestationName][period] = 0;
            }
        });

    });

};

var buildOpenFiscaEntity = function(mesAidesEntity, mappingSchema, situation) {
    var result = {};
    _.forEach(mappingSchema, function(definition, openFiscaKey) {
        var params = _.isString(definition) ? { src: definition } : definition;

        if (params.values) {
            result[openFiscaKey] = params.values[mesAidesEntity[params.src]];
        } else if (params.fn) {
            result[openFiscaKey] = params.src ? params.fn(mesAidesEntity[params.src], mesAidesEntity, situation) : params.fn(mesAidesEntity, situation);
        } else {
            result[openFiscaKey] = mesAidesEntity[params.src];
        }

        if (params.round && _.isNumber(result[openFiscaKey])) {
            result[openFiscaKey] = Math.round(result[openFiscaKey]);
        }

        // Supprime les null (mal supportés par OpenFisca)
        if (isNotValidValue(result[openFiscaKey])) {
            if ('default' in params) result[openFiscaKey] = params.default;
            else delete result[openFiscaKey];
        }
    });
    return result;
};

var mapIndividus = exports.mapIndividus = function (situation) {
    var individus = _.filter(situation.individus, function(individu) {
        return common.isIndividuValid(individu, situation);
    });

    return _.map(individus, function(individu) {
        if (individu.role === 'demandeur') {
            _.extend(individu, situation.patrimoine);

            var revenusLocatifs = situation.patrimoine.revenusLocatifs.map(function(revenu) {
                revenu.type = 'revenusLocatifs';
                return revenu;
            });

            var revenusDuCapital = situation.patrimoine.revenusDuCapital.map(function(revenu) {
                revenu.type = 'revenusDuCapital';
                return revenu;
            });

            individu.ressources = individu.ressources.concat(revenusLocatifs, revenusDuCapital);
        }
        var openFiscaIndividu = buildOpenFiscaEntity(individu, individuSchema, situation);
        applyRessources(individu, openFiscaIndividu, ressourcesMap.individu, situation.dateDeValeur);
        return openFiscaIndividu;
    });
};

var mapFamille = function(situation) {
    var famille = buildOpenFiscaEntity(situation, familleSchema, situation);
    var ressources = [];
    _.forEach(situation.individus, function(individu) {
        ressources = ressources.concat(individu.ressources);
    });
    applyRessources({ ressources: ressources }, famille, ressourcesMap.famille, situation.dateDeValeur);
    setNonInjectedPrestationsToZero(famille, situation.dateDeValeur);

    return famille;
};

var mapFoyerFiscal = exports.mapFoyerFiscal = function(situation) {
    var foyerFiscal = buildOpenFiscaEntity(situation, foyerSchema, situation);
    var ressources = [];
    _.forEach(situation.individus, function(individu) {
        ressources = ressources.concat(individu.ressources);
    });
    applyRessources({ ressources: ressources }, foyerFiscal, ressourcesMap.foyerFiscal, situation.dateDeValeur);

    return foyerFiscal;
};

exports.buildOpenFiscaRequest = function(situation, debug) {
        return {
        intermediate_variables: debug,
        labels: true,
        scenarios: [{
            test_case: {
                familles: [mapFamille(situation)],
                foyers_fiscaux: [mapFoyerFiscal(situation)],
                individus: mapIndividus(situation),
                menages: [buildOpenFiscaEntity(situation, menageSchema, situation)]
            },
            period: 'month:' + periodTools.toOpenFiscaFormat(situation.dateDeValeur)
        }],
        variables: _.keys(PRESTATIONS)
    };
};
<commit_msg>Pass situation as argument to applyRessources<commit_after>var _ = require('lodash');
var moment = require('frozen-moment');
var periodTools = require('./periodTools');
var ressourcesMap = require('./ressources');
var individuSchema = require('./individu');
var menageSchema = require('./menage');
var familleSchema = require('./famille');
var foyerSchema = require('./foyerFiscal');
var common = require('./common');
var PRESTATIONS = require('../prestations');

var isNotValidValue = function(value) {
    return _.isNaN(value) || _.isUndefined(value) || value === null;
};

var getPeriods = function(dateDeValeur) {
    dateDeValeur = moment(dateDeValeur).freeze();
    return {
        moisCourant: {
            key: dateDeValeur.format('YYYY-MM')
        },
        moisPrecedent: {
            key: dateDeValeur.subtract(1, 'months').format('YYYY-MM')
        },
        moisMMoins2: {
            key: dateDeValeur.subtract(2, 'months').format('YYYY-MM')
        },
        moisMMoins3: {
            key: dateDeValeur.subtract(3, 'months').format('YYYY-MM')
        },
       anneeGlissante: {
            debut: dateDeValeur.subtract(1, 'years').subtract(1, 'months'),
            fin: dateDeValeur,
            key: 'year:' + dateDeValeur.subtract(1, 'years').format('YYYY-MM')
        },
        anneeFiscalReference: {
            key: dateDeValeur.subtract(2, 'years').year()
        },
        trimestreGlissant: {
            debut: dateDeValeur.subtract(3, 'months').subtract(1, 'months'),
            fin: dateDeValeur,
            key: dateDeValeur.subtract(3, 'months').format('YYYY-MM') + ':3'
        }
    };
};

var applyRessources = function(mesAidesEntity, openFiscaEntity, mappingSchema, situation) {
    var dateDeValeur = situation.dateDeValeur;
    var periods = getPeriods(dateDeValeur);

    function isPeriodWithin(agregatKey, periode) {
        return periode.isAfter(periods[agregatKey].debut, 'month') && periode.isBefore(periods[agregatKey].fin, 'month');
    }

    var ressourcesByType = _.groupBy(mesAidesEntity.ressources, 'type');

    _.forEach(mappingSchema, function(definitions, openFiscaKey) {
        if (! _.isArray(definitions)) {
            definitions = [definitions];
        }

        _.forEach(definitions, function(definition) {

            var srcKey = definition.src || definition;
            var fn = definition.fn;

            if (ressourcesByType[srcKey]) {
                var result = {};
                var agr = {};

                _.forEach(ressourcesByType[srcKey], function(item) {
                    if (item.periode) {
                        // Période d'origine
                        if (!result[item.periode]) result[item.periode] = 0;
                        result[item.periode] += item.montant;

                        // Aggrégats
                        _.keys(periods).forEach(function (agregatKey) {
                            if (agregatKey != 'anneeFiscalReference' && isPeriodWithin(agregatKey, moment(item.periode, 'YYYY-MM'))) {
                                if (!(agregatKey in agr)) agr[agregatKey] = 0;
                                agr[agregatKey] += fn ? fn(item.montant) : item.montant;
                            }
                        });
                    }
                });

                _.forEach(result, function(montant, periode) {
                    // Applique la transformation
                    if (fn) montant = fn(montant);
                    result[periode] = montant;
                });

                // On prolonge la ressource perçue quand c'est possible
                if (result[periods.moisPrecedent.key] && !_.contains(mesAidesEntity.interruptedRessources, srcKey)) {
                    result[periods.moisCourant.key] = result[periods.moisPrecedent.key];
                }

                if (_.size(result) > 0) {
                    if (_.size(openFiscaEntity[openFiscaKey]) > 0) {
                        openFiscaEntity[openFiscaKey] = _.extend(openFiscaEntity[openFiscaKey],result);
                    } else {
                        openFiscaEntity[openFiscaKey] = result;
                    }
                }

                _.forEach(agr, function (value, agregatKey) {
                    openFiscaEntity[openFiscaKey][periods[agregatKey].key] = value;
                });

                // On suppose que les revenus pour l'année fiscale de référence sont les mêmes que pour les 12 derniers mois, si ces revenus n'ont pas encore
                // été renseignés
                var anneeGlissanteKey = periods.anneeGlissante.key,
                    anneeFiscaleRefKey = periods.anneeFiscalReference.key;
                if (! openFiscaEntity[openFiscaKey][anneeFiscaleRefKey]) {
                    openFiscaEntity[openFiscaKey][anneeFiscaleRefKey] = openFiscaEntity[openFiscaKey][anneeGlissanteKey];
                }
            }
        });
    });
};

var setNonInjectedPrestationsToZero = function(openFiscaEntity, dateDeValeur) {
    var periods = getPeriods(dateDeValeur);
    var concernedPeriods = ['moisPrecedent', 'moisMMoins2', 'moisMMoins3', 'anneeGlissante'];

    _.forEach(concernedPeriods, function(periodKey) {
        var period = periods[periodKey].key;
        var prestations = _.pick(PRESTATIONS, function(definition) {
            return definition.type == Number;
        });

        _.forEach(prestations, function(definition, prestationName) {
            if (! openFiscaEntity[prestationName]) {
                openFiscaEntity[prestationName] = {};
            }
            if (! openFiscaEntity[prestationName][period]) {
                openFiscaEntity[prestationName][period] = 0;
            }
        });

    });

};

var buildOpenFiscaEntity = function(mesAidesEntity, mappingSchema, situation) {
    var result = {};
    _.forEach(mappingSchema, function(definition, openFiscaKey) {
        var params = _.isString(definition) ? { src: definition } : definition;

        if (params.values) {
            result[openFiscaKey] = params.values[mesAidesEntity[params.src]];
        } else if (params.fn) {
            result[openFiscaKey] = params.src ? params.fn(mesAidesEntity[params.src], mesAidesEntity, situation) : params.fn(mesAidesEntity, situation);
        } else {
            result[openFiscaKey] = mesAidesEntity[params.src];
        }

        if (params.round && _.isNumber(result[openFiscaKey])) {
            result[openFiscaKey] = Math.round(result[openFiscaKey]);
        }

        // Supprime les null (mal supportés par OpenFisca)
        if (isNotValidValue(result[openFiscaKey])) {
            if ('default' in params) result[openFiscaKey] = params.default;
            else delete result[openFiscaKey];
        }
    });
    return result;
};

var mapIndividus = exports.mapIndividus = function (situation) {
    var individus = _.filter(situation.individus, function(individu) {
        return common.isIndividuValid(individu, situation);
    });

    return _.map(individus, function(individu) {
        if (individu.role === 'demandeur') {
            _.extend(individu, situation.patrimoine);

            var revenusLocatifs = situation.patrimoine.revenusLocatifs.map(function(revenu) {
                revenu.type = 'revenusLocatifs';
                return revenu;
            });

            var revenusDuCapital = situation.patrimoine.revenusDuCapital.map(function(revenu) {
                revenu.type = 'revenusDuCapital';
                return revenu;
            });

            individu.ressources = individu.ressources.concat(revenusLocatifs, revenusDuCapital);
        }
        var openFiscaIndividu = buildOpenFiscaEntity(individu, individuSchema, situation);
        applyRessources(individu, openFiscaIndividu, ressourcesMap.individu, situation);
        return openFiscaIndividu;
    });
};

var mapFamille = function(situation) {
    var famille = buildOpenFiscaEntity(situation, familleSchema, situation);
    var ressources = [];
    _.forEach(situation.individus, function(individu) {
        ressources = ressources.concat(individu.ressources);
    });
    applyRessources({ ressources: ressources }, famille, ressourcesMap.famille, situation);
    setNonInjectedPrestationsToZero(famille, situation.dateDeValeur);

    return famille;
};

var mapFoyerFiscal = exports.mapFoyerFiscal = function(situation) {
    var foyerFiscal = buildOpenFiscaEntity(situation, foyerSchema, situation);
    var ressources = [];
    _.forEach(situation.individus, function(individu) {
        ressources = ressources.concat(individu.ressources);
    });
    applyRessources({ ressources: ressources }, foyerFiscal, ressourcesMap.foyerFiscal, situation);

    return foyerFiscal;
};

exports.buildOpenFiscaRequest = function(situation, debug) {
        return {
        intermediate_variables: debug,
        labels: true,
        scenarios: [{
            test_case: {
                familles: [mapFamille(situation)],
                foyers_fiscaux: [mapFoyerFiscal(situation)],
                individus: mapIndividus(situation),
                menages: [buildOpenFiscaEntity(situation, menageSchema, situation)]
            },
            period: 'month:' + periodTools.toOpenFiscaFormat(situation.dateDeValeur)
        }],
        variables: _.keys(PRESTATIONS)
    };
};
<|endoftext|>


<commit_before>"use strict";

(function() {
    // borrowed from http://codepen.io/gapcode/pen/vEJNZN, condensed
    function detectIE() {
        var ua = window.navigator.userAgent;
        return (ua.indexOf('MSIE ') > 0) || (ua.indexOf('Trident/') > 0) || (ua.indexOf('Edge/') > 0);
    }

    /**
     * add case insensitive contains expression to jQuery
     */
    jQuery.expr[':'].containsCI = function(a, i, m) {
        return jQuery(a).text().toUpperCase().indexOf(m[3].toUpperCase()) >= 0;
    };

    /**
     * filter listed excerpts by the filter word from the filter bar
     */
    jQuery(document).ready(function() {
            var optGroupFilter = function() {
            var selectionBox = jQuery("#opt_group_filter");
            jQuery("#opt_group_filter_div").attr('class', '');
            jQuery(selectionBox).append('<option value="">Show All</option>');
            jQuery("#id_existing_excerpts").children('optgroup').each(
                    function(count, el){
                        var label = jQuery(el).attr('label');
                        jQuery(selectionBox).append('<option value="' + label + '">' + label + '</option>');
                    }
            );
            $('#id_existing_excerpts').filterGroups({groupSelector: '#opt_group_filter'});
            selectionBox.on('change', function(){
                wordFilter();
            });
        };
        var wordFilter = function() {
            var excerptListFilterField = jQuery('input#excerptListFilterField');
            var excerptListFilterFieldClearer = jQuery('span#excerptListFilterFieldClearer');
            var excerptListFieldOptions = jQuery('select#id_existing_excerpts > optgroup > option');

            var hideElements = function (filterWord) {
                excerptListFilterFieldClearer.show();
                excerptListFieldOptions.hide().filter(':containsCI(' + filterWord + ')').show();
            };
            var showAllElements = function () {
                excerptListFieldOptions.show();
                excerptListFilterFieldClearer.hide();
            };

            // ################# IE patch... #################
            // Inspired by: http://ajax911.com/hide-options-selecbox-jquery/
            if (detectIE()) {
                showAllElements = function () {
                    excerptListFieldOptions.each(function (index, val) {
                        if (this.nodeName.toUpperCase() === 'OPTION') {
                            var span = $(this).parent();
                            var opt = this;
                            if ($(this).parent().is('span')) {
                                $(opt).show();
                                $(span).replaceWith(opt);
                            }
                        }
                    });
                };

                //hide elements
                hideElements = function (filterWord) {
                    excerptListFieldOptions.each(function (index, val) {
                        // wrap all
                        if ($(this).is('option') && (!$(this).parent().is('span'))) {
                            $(this).wrap('<span>');
                        }
                    });
                    var selectIEOptions = jQuery('select#id_existing_excerpts > optgroup > span > option');
                    // unwrap matching ones
                    selectIEOptions.filter(':containsCI(' + filterWord + ')').each(function () {
                        var elem = $(this);
                        var span = elem.parent();
                        $(elem).show();
                        $(span).replaceWith(elem);
                    });
                };
            }
            // ################# ...IE patch end #################

            function filterOptions(excerptListFilterField) {
                var filterWord = excerptListFilterField.val();
                if (filterWord.length > 0) {
                    hideElements(filterWord);
                } else {
                    showAllElements();
                }
            }

            if (excerptListFilterField && excerptListFilterFieldClearer) {
                excerptListFilterField.bind('change paste keyup input', function () {
                    filterOptions(excerptListFilterField);
                });
                // clear field icon
                excerptListFilterFieldClearer.bind('click', function () {
                    excerptListFilterField.val('');
                    filterOptions(excerptListFilterField);
                });
                // execute filter for the first time -> may be there will be a filter word inside the filed after reload entered by the browser
                filterOptions(excerptListFilterField);
            }
        };
        optGroupFilter();
        wordFilter();
    });
})();
<commit_msg>fix indentation<commit_after>"use strict";

(function() {
    // borrowed from http://codepen.io/gapcode/pen/vEJNZN, condensed
    function detectIE() {
        var ua = window.navigator.userAgent;
        return (ua.indexOf('MSIE ') > 0) || (ua.indexOf('Trident/') > 0) || (ua.indexOf('Edge/') > 0);
    }

    /**
     * add case insensitive contains expression to jQuery
     */
    jQuery.expr[':'].containsCI = function(a, i, m) {
        return jQuery(a).text().toUpperCase().indexOf(m[3].toUpperCase()) >= 0;
    };

    /**
     * filter listed excerpts by the filter word from the filter bar
     */
    jQuery(document).ready(function() {
        var optGroupFilter = function() {
            var selectionBox = jQuery("#opt_group_filter");
            jQuery("#opt_group_filter_div").attr('class', '');
            jQuery(selectionBox).append('<option value="">Show All</option>');
            jQuery("#id_existing_excerpts").children('optgroup').each(
                    function(count, el){
                        var label = jQuery(el).attr('label');
                        jQuery(selectionBox).append('<option value="' + label + '">' + label + '</option>');
                    }
            );
            $('#id_existing_excerpts').filterGroups({groupSelector: '#opt_group_filter'});
            selectionBox.on('change', function(){
                wordFilter();
            });
        };
        var wordFilter = function() {
            var excerptListFilterField = jQuery('input#excerptListFilterField');
            var excerptListFilterFieldClearer = jQuery('span#excerptListFilterFieldClearer');
            var excerptListFieldOptions = jQuery('select#id_existing_excerpts > optgroup > option');

            var hideElements = function (filterWord) {
                excerptListFilterFieldClearer.show();
                excerptListFieldOptions.hide().filter(':containsCI(' + filterWord + ')').show();
            };
            var showAllElements = function () {
                excerptListFieldOptions.show();
                excerptListFilterFieldClearer.hide();
            };

            // ################# IE patch... #################
            // Inspired by: http://ajax911.com/hide-options-selecbox-jquery/
            if (detectIE()) {
                showAllElements = function () {
                    excerptListFieldOptions.each(function (index, val) {
                        if (this.nodeName.toUpperCase() === 'OPTION') {
                            var span = $(this).parent();
                            var opt = this;
                            if ($(this).parent().is('span')) {
                                $(opt).show();
                                $(span).replaceWith(opt);
                            }
                        }
                    });
                };

                //hide elements
                hideElements = function (filterWord) {
                    excerptListFieldOptions.each(function (index, val) {
                        // wrap all
                        if ($(this).is('option') && (!$(this).parent().is('span'))) {
                            $(this).wrap('<span>');
                        }
                    });
                    var selectIEOptions = jQuery('select#id_existing_excerpts > optgroup > span > option');
                    // unwrap matching ones
                    selectIEOptions.filter(':containsCI(' + filterWord + ')').each(function () {
                        var elem = $(this);
                        var span = elem.parent();
                        $(elem).show();
                        $(span).replaceWith(elem);
                    });
                };
            }
            // ################# ...IE patch end #################

            function filterOptions(excerptListFilterField) {
                var filterWord = excerptListFilterField.val();
                if (filterWord.length > 0) {
                    hideElements(filterWord);
                } else {
                    showAllElements();
                }
            }

            if (excerptListFilterField && excerptListFilterFieldClearer) {
                excerptListFilterField.bind('change paste keyup input', function () {
                    filterOptions(excerptListFilterField);
                });
                // clear field icon
                excerptListFilterFieldClearer.bind('click', function () {
                    excerptListFilterField.val('');
                    filterOptions(excerptListFilterField);
                });
                // execute filter for the first time -> may be there will be a filter word inside the filed after reload entered by the browser
                filterOptions(excerptListFilterField);
            }
        };
        optGroupFilter();
        wordFilter();
    });
})();
<|endoftext|>


<commit_before>const RedisListWrapper = require('./list');
const RedisCollectionWrapper = require('./collection');
let connection = {};

function closeConnection(client) {
	if (client.hasOwnProperty('connection')) {
		client.connection.end();
	}
}

function wrapRedis(connection) {
  return {
    connection: connection,
		close: closeConnection,
    List: RedisListWrapper(connection),
		Collection: RedisCollectionWrapper(connection),
  }
}

module.exports = wrapRedis;
<commit_msg>closing redis client connections properly<commit_after>const RedisListWrapper = require('./list');
const RedisCollectionWrapper = require('./collection');
let connection = {};

function closeConnection(client) {
	if (client.hasOwnProperty('connection')) {
		client.connection.end(true);
	}
}

function wrapRedis(connection) {
  return {
    connection: connection,
		close: closeConnection,
    List: RedisListWrapper(connection),
		Collection: RedisCollectionWrapper(connection),
  }
}

module.exports = wrapRedis;
<|endoftext|>


<commit_before>var ics = require('./ics-builder');
var fs = require('fs')
    , filename = process.argv[2];

/**
 * Takes in any time and converts it from a 12HR format to the 24HR format for 7:00PM is 19:00 etc
 * @param {String} time
 * @return {String} 24HR formatted time
 */
function convertTo24Hour(time) {
    var hours = parseInt(time.substr(0, 2));
    if(time.indexOf('AM') != -1 && hours == 12) {
        time = time.replace('12', '0');
    }
    if(time.indexOf('PM')  != -1 && hours < 12) {
        time = time.replace(hours, (hours + 12));
    }
    return time.replace(/(AM|PM)/, '');
}

/**
 * Takes any date and turns it into the correct format, so javascript gets the right date.
 * MM/DD/YYYY is the format the javascript object reads or YYYY/MM/DD
 * @param {String} startDate
 * @param {String} endDate
 * @return {String Array} [startDate, endDate]  with format MM/DD/YYYY or YYYY/MM/DD
 */
function fixDateFormat(sem_start, sem_end) {
    if (sem_start.indexOf('-') > -1) {
        return [sem_start,sem_end]
    }
    //assume format is either YYYY/MM/DD TO START OR MM/DD/YYYY
    startDateSplit = sem_start.split("/");
    endDateSplit = sem_end.split("/");

    startMonth = parseInt(startDateSplit[0]);
    startDay = parseInt(startDateSplit[1]);
    startYear = parseInt(startDateSplit[2]);

    endMonth = parseInt(endDateSplit[0]);
    endDay = parseInt(endDateSplit[1]);
    endYear = parseInt(endDateSplit[2]);

    // It is YYYY/MM/DD, because the 0th index is a year or
    //If the difference is 3 months (Fall/Winter), 1 month (Spring/Summer) or -5 months (multi-semester(Sept-Apr))
    // and the semester starts in [1,5,7,9], Check is month is Jan,May,July or Sept
    if(startMonth.toString().length === 4 ||
        ((endMonth - startMonth === 3 || endMonth - startMonth === 1 || endMonth - startMonth === -5) && [1,5,7,9].indexOf(startMonth) > -1)){
        return [sem_start, sem_end]
    } else if (startMonth > 12 || endMonth > 12 || (startYear === endYear && startMonth > endMonth)) {
        // Date is DD/MM/YYYY because month can't be > 12, so switch the month and day around
        return [[startDay, startMonth, startYear].join("/"),[endDay, endMonth, endYear].join("/")];

        //If the difference is 3 months (Fall/Winter), 1 month (Spring/Summer) or -5 months (multi-semester(Sept-Apr)),
        // in this case the day is in the month slot and the semester starts in [1,5,7,9], Check is month is Jan,May,July or Sept
    } else if ((endDay - startDay === 3 || endDay - startDay === 1 || endDay - startDay === -5) && [1,5,7,9].indexOf(startDay) > -1) {
        // DD/MM/YYYY is the format found because the DD index [1] slot is a Month not a day, so switch the month and day around
        return [[startDay, startMonth, startYear].join("/"),[endDay, endMonth, endYear].join("/")]
    }

    //Default
    return [sem_start, sem_end]
}

/**
 * Takes in the raw data of timetable and get all the important details including, course name, professor, times etc.
 * @param {String} text
 * @return {Object/JSON} timetable
 */
function parse(text){
    var _wsRegex = /\s+/;
    var _ampm = /:\d{2}[aAPp][mM]/;
    var _courseTitleLookAhead = /(?=\b(?:\w{2,}\ \w{1,5})\ -\ (?:[^\r\n]+)\b)/g;
    var _courseTitleRegex = /(?:\b(\w{2,}\ \w{1,5})\ -\ ([^\r\n]+)\b)/;
    var _dayOfWeekRegex = /([MoTuhWeFrSaS]{2,6})/;
    var _timeRegex  =  _ampm.exec(text) ? /([012]?\d\:[0-5]\d[AP]M)/ : /([012]?\d\:[0-5]\d)/;
    var _timeDayPairRegex = new RegExp(_dayOfWeekRegex.source + _wsRegex.source + _timeRegex.source +  _wsRegex.source + "-" +  _wsRegex.source + _timeRegex.source);
    var _timeDayPairLookAhead =  _ampm.exec(text) ? /(?=\b(?:[MoTuhWeFrSaS]{2,6})\s+(?:[012]?\d\:[0-5]\d[AP]M)\s+-\s+(?:[012]?\d\:[0-5]\d[AP]M)\b)/ : /(?=\b(?:[MoTuhWeFrSaS]{2,6})\s+(?:[012]?\d\:[0-5]\d)\s+-\s+(?:[012]?\d\:[0-5]\d)\b)/;
    var _courseCodeLookAhead = /(?=\b\d{5}\b)/;
    var _timeDayPairOrCourseCodeLookAhead = new RegExp(_courseCodeLookAhead.source + "|" + _timeDayPairLookAhead.source ,"g");
    var _locationRegex = /([A-Z0-9]{2,5}\s+[A-Z0-9]{2,5})/;
    var _profRegex = /((\b[A-Za-z]+\s)(?:[A-Za-z\-.]+\s?)+|(\b[A-Za-z]+\s))/;
    var _locationProfPair = new RegExp(_locationRegex.source + _wsRegex.source + _profRegex.source);
    var _semesterRegex = /((?:\d{2}\/\d{2}\/\d{4})|(?:\d{4}\/\d{2}\/\d{2})|(?:\d{4}-\d{2}-\d{2}))/;
    var _semesterPairRegex = new RegExp(_semesterRegex.source +  _wsRegex.source + "-" +  _wsRegex.source +  _semesterRegex.source);
    var _sectionTypeRegex = /(\d+)\s+([A-Z]{1}\d+)\s+(?=Lecture|Tutorial|Laboratory)([A-Za-z]{3,}\b)/;
    var _dayRegex = /(Mo)|(Tu)|(We)|(Th)|(Fr)|(Sa)|(Su)/g;
    var _weekdays = {"Mo":"Monday","Tu":"Tuesday","We":"Wednesday","Th":"Thursday","Fr":"Friday","Sa":"Saturday","Su":"Sunday"};
    var timetable = [];
    // Breaks all the courses up. Looks at all the detail after a course name
    var courses = text.split(_courseTitleLookAhead);

    for (var i = 0; i < courses.length; i++) {
        var courseText = courses[i];
        var course = _courseTitleRegex.exec(courseText);
        // Breaks all classes up, looks at all the details after the Course Code or time in a course
        var classes = courseText.split(_timeDayPairOrCourseCodeLookAhead);

        if (!course) continue;

        var currentSection = _sectionTypeRegex.exec(courseText);

        for (var j = 0; j < classes.length; j++) {
            var locationProfPair = _locationProfPair.exec(classes[j]);
            var time = _timeDayPairRegex.exec(classes[j]);
            var semester = _semesterPairRegex.exec(classes[j]);
            var section = _sectionTypeRegex.exec(classes[j]);
            // Check is the current classes segment is a section if it is update the current Section
            currentSection = section ? section : currentSection;

            if (!locationProfPair || !time || !semester) continue;

            semester = fixDateFormat(semester[1], semester[2]);
            var where = locationProfPair[1];
            var professor = locationProfPair[2];
            // Changes McMasters MoTuWe format to full weekdays
            var days = time[1].replace(_dayRegex, function fullWeekday(x){return _weekdays[x] + ",";});
            var daysArray = days.substring(0, days.length-1).split(",");

            for (var k = 0; k < daysArray.length ; k++) {
                var startTimeSplit = convertTo24Hour(time[2]).split(":");
                var startTimeObj = {'hour': parseInt(startTimeSplit[0], 10), 'minute': parseInt(startTimeSplit[1], 10)};
                var endTimeSplit = convertTo24Hour(time[3]).split(":");
                var endTimeObj = {'hour': parseInt(endTimeSplit[0], 10), 'minute': parseInt(endTimeSplit[1], 10)};
                var sem_start = new Date(semester[0]);
                var sem_end = new Date(semester[1]);
                var semesterStartObj = { year: sem_start.getFullYear(), month: sem_start.getMonth(), day: sem_start.getDate()};
                var semesterEndObj = { year: sem_end.getFullYear(), month: sem_end.getMonth(), day: sem_end.getDate()};
                var courseSplit = course[1].split(" ");
                timetable.push({
                    'course_code_faculty' : courseSplit[0],
                    'course_code_number' : courseSplit[1],
                    'course_number' : currentSection[1],
                    'course_name' : course[2],
                    'semester_start' : semesterStartObj,
                    'semester_end' : semesterEndObj,
                    'where' : where,
                    'professor': professor,
                    'day' : daysArray[k],
                    'start_time' : startTimeObj,
                    'end_time' : endTimeObj,
                    'class_section' : currentSection[2],
                    'class_type' : currentSection[3]
                });
            }}
    }
    return timetable;
}

fs.readFile(filename, 'utf8', function(err, data) {
    if (err) throw err;
    console.log(ics.buildICS(parse(data)));
});

module.exports.parse = parse;<commit_msg>Trimmed some fat from the fixDateFormate algorithm<commit_after>var ics = require('./ics-builder');
var fs = require('fs')
    , filename = process.argv[2];
/**
 * Takes in any time and converts it from a 12HR format to the 24HR format for 7:00PM is 19:00 etc
 * @param {String} time
 * @return {String} 24HR formatted time
 */
function convertTo24Hour(time) {
    var hours = parseInt(time.substr(0, 2));
    if(time.indexOf('AM') != -1 && hours == 12) {
        time = time.replace('12', '0');
    }
    if(time.indexOf('PM')  != -1 && hours < 12) {
        time = time.replace(hours, (hours + 12));
    }
    return time.replace(/(AM|PM)/, '');
}
/**
 * Takes any date and turns it into the correct format, so javascript gets the right date.
 * MM/DD/YYYY is the format the javascript object reads or YYYY/MM/DD
 * @param {String} startDate
 * @param {String} endDate
 * @return {String Array} [startDate, endDate]  with format MM/DD/YYYY or YYYY/MM/DD or YYYY-MM-DD
 */
function fixDateFormat(sem_start, sem_end) {
    var startDateSplit = sem_start.split("/");
    var endDateSplit = sem_end.split("/");

    var startMonth = parseInt(startDateSplit[0]);
    var startDay = parseInt(startDateSplit[1]);
    var startYear = parseInt(startDateSplit[2]);

    var endMonth = parseInt(endDateSplit[0]);
    var endDay = parseInt(endDateSplit[1]);
    var endYear = parseInt(endDateSplit[2]);

    var monthDiff = endMonth - startMonth;
    var dayDiff = endDay - startDay;
    // If it contains the '-' or the first slot is YYYY it is already in correct format
    if (sem_start.indexOf('-') > -1 || startMonth.toString().length === 4) {
        return [sem_start, sem_end]
    } else
        // Date is DD/MM/YYYY because month can't be > 12, so switch the month and day around or
        // If the difference is 3 months (Fall/Winter), 1 month (Spring/Summer) or -5 months (multi-semester(Sept-Apr)),
        // in this case the day is in the month slot and the semester starts in [1,5,7,9], Check is month is Jan,May,July or Sept
        if (startMonth > 12 || endMonth > 12 || (startYear === endYear && startMonth > endMonth) ||
    ((dayDiff === 3 || dayDiff === 1 || dayDiff === -5) && [1, 5, 7, 9].indexOf(startDay) > -1)) {
        return [[startDay, startMonth, startYear].join("/"),[endDay, endMonth, endYear].join("/")];
    }
    return [sem_start, sem_end]
}
/**
 * Takes in the raw data of timetable and get all the important details including, course name, professor, times etc.
 * @param {String} text
 * @return {Object/JSON} timetable
 */
function parse(text){
    var _wsRegex = /\s+/;
    var _ampm = /:\d{2}[aAPp][mM]/;
    var _courseTitleLookAhead = /(?=\b(?:\w{2,}\ \w{1,5})\ -\ (?:[^\r\n]+)\b)/g;
    var _courseTitleRegex = /(?:\b(\w{2,}\ \w{1,5})\ -\ ([^\r\n]+)\b)/;
    var _dayOfWeekRegex = /([MoTuhWeFrSaS]{2,6})/;
    var _timeRegex  =  _ampm.exec(text) ? /([012]?\d\:[0-5]\d[AP]M)/ : /([012]?\d\:[0-5]\d)/;
    var _timeDayPairRegex = new RegExp(_dayOfWeekRegex.source + _wsRegex.source + _timeRegex.source +  _wsRegex.source + "-" +  _wsRegex.source + _timeRegex.source);
    var _timeDayPairLookAhead =  _ampm.exec(text) ? /(?=\b(?:[MoTuhWeFrSaS]{2,6})\s+(?:[012]?\d\:[0-5]\d[AP]M)\s+-\s+(?:[012]?\d\:[0-5]\d[AP]M)\b)/ : /(?=\b(?:[MoTuhWeFrSaS]{2,6})\s+(?:[012]?\d\:[0-5]\d)\s+-\s+(?:[012]?\d\:[0-5]\d)\b)/;
    var _courseCodeLookAhead = /(?=\b\d{5}\b)/;
    var _timeDayPairOrCourseCodeLookAhead = new RegExp(_courseCodeLookAhead.source + "|" + _timeDayPairLookAhead.source ,"g");
    var _locationRegex = /([A-Z0-9]{2,5}\s+[A-Z0-9]{2,5})/;
    var _profRegex = /((\b[A-Za-z]+\s)(?:[A-Za-z\-.]+\s?)+|(\b[A-Za-z]+\s))/;
    var _locationProfPair = new RegExp(_locationRegex.source + _wsRegex.source + _profRegex.source);
    var _semesterRegex = /((?:\d{2}\/\d{2}\/\d{4})|(?:\d{4}\/\d{2}\/\d{2})|(?:\d{4}-\d{2}-\d{2}))/;
    var _semesterPairRegex = new RegExp(_semesterRegex.source +  _wsRegex.source + "-" +  _wsRegex.source +  _semesterRegex.source);
    var _sectionTypeRegex = /(\d+)\s+([A-Z]{1}\d+)\s+(?=Lecture|Tutorial|Laboratory)([A-Za-z]{3,}\b)/;
    var _dayRegex = /(Mo)|(Tu)|(We)|(Th)|(Fr)|(Sa)|(Su)/g;
    var _weekdays = {"Mo":"Monday", "Tu":"Tuesday", "We":"Wednesday", "Th":"Thursday", "Fr":"Friday", "Sa":"Saturday", "Su":"Sunday"};
    var timetable = [];
    // Breaks all the courses up. Looks at all the detail after a course name
    var courses = text.split(_courseTitleLookAhead);

    for (var i = 0; i < courses.length; i++) {
        var courseText = courses[i];
        var course = _courseTitleRegex.exec(courseText);
        // Breaks all classes up, looks at all the details after the Course Code or time in a course
        var classes = courseText.split(_timeDayPairOrCourseCodeLookAhead);

        if (!course) continue;

        var currentSection = _sectionTypeRegex.exec(courseText);

        for (var j = 0; j < classes.length; j++) {
            var locationProfPair = _locationProfPair.exec(classes[j]);
            var time = _timeDayPairRegex.exec(classes[j]);
            var semester = _semesterPairRegex.exec(classes[j]);
            var section = _sectionTypeRegex.exec(classes[j]);
            // Check is the current classes segment is a section if it is update the current Section
            currentSection = section ? section : currentSection;

            if (!locationProfPair || !time || !semester) continue;

            semester = fixDateFormat(semester[1], semester[2]);
            var where = locationProfPair[1];
            var professor = locationProfPair[2];
            // Changes McMasters MoTuWe format to full weekdays
            var days = time[1].replace(_dayRegex, function fullWeekday(x){return _weekdays[x] + ",";});
            var daysArray = days.substring(0, days.length-1).split(",");

            for (var k = 0; k < daysArray.length ; k++) {
                var startTimeSplit = convertTo24Hour(time[2]).split(":");
                var startTimeObj = {'hour': parseInt(startTimeSplit[0], 10), 'minute': parseInt(startTimeSplit[1], 10)};
                var endTimeSplit = convertTo24Hour(time[3]).split(":");
                var endTimeObj = {'hour': parseInt(endTimeSplit[0], 10), 'minute': parseInt(endTimeSplit[1], 10)};
                var sem_start = new Date(semester[0]);
                var sem_end = new Date(semester[1]);
                var semesterStartObj = { year: sem_start.getFullYear(), month: sem_start.getMonth(), day: sem_start.getDate()};
                var semesterEndObj = { year: sem_end.getFullYear(), month: sem_end.getMonth(), day: sem_end.getDate()};
                var courseSplit = course[1].split(" ");
                timetable.push({
                    'course_code_faculty' : courseSplit[0],
                    'course_code_number' : courseSplit[1],
                    'course_number' : currentSection[1],
                    'course_name' : course[2],
                    'semester_start' : semesterStartObj,
                    'semester_end' : semesterEndObj,
                    'where' : where,
                    'professor': professor,
                    'day' : daysArray[k],
                    'start_time' : startTimeObj,
                    'end_time' : endTimeObj,
                    'class_section' : currentSection[2],
                    'class_type' : currentSection[3]
                });
            }}
    }
    return timetable;
}

fs.readFile(filename, 'utf8', function(err, data) {
    if (err) throw err;
    console.log(ics.buildICS(parse(data)));
});

module.exports.parse = parse;<|endoftext|>