<commit_before>/*
 *
 * (c) Copyright Ascensio System SIA 2010-2019
 *
 * This program is a free software product. You can redistribute it and/or
 * modify it under the terms of the GNU Affero General Public License (AGPL)
 * version 3 as published by the Free Software Foundation. In accordance with
 * Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect
 * that Ascensio System SIA expressly excludes the warranty of non-infringement
 * of any third-party rights.
 *
 * This program is distributed WITHOUT ANY WARRANTY; without even the implied
 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR  PURPOSE. For
 * details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
 *
 * You can contact Ascensio System SIA at 20A-12 Ernesta Birznieka-Upisha
 * street, Riga, Latvia, EU, LV-1050.
 *
 * The  interactive user interfaces in modified source and object code versions
 * of the Program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU AGPL version 3.
 *
 * Pursuant to Section 7(b) of the License you must retain the original Product
 * logo when distributing the program. Pursuant to Section 7(e) we decline to
 * grant you any rights under trademark law for use of our trademarks.
 *
 * All the Product's GUI elements, including illustrations and icon sets, as
 * well as technical writing content are licensed under the terms of the
 * Creative Commons Attribution-ShareAlike 4.0 International. See the License
 * terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
 *
*/
/**
 * User: Julia.Radzhabova
 * Date: 14.12.17
 */

define([
    'common/main/lib/util/utils',
    'common/main/lib/component/BaseView',
    'common/main/lib/component/Layout',
    'common/main/lib/component/TreeView'
], function (template) {
    'use strict';

    DE.Views.Navigation = Common.UI.BaseView.extend(_.extend({
        el: '#left-panel-navigation',

        storeNavigation: undefined,
        template: _.template([
            '
',
                // '
<%= scope.strNavigate %>
',
                '
',
                '
',
            '
'
        ].join('')),

        initialize: function(options) {
            _.extend(this, options);
            Common.UI.BaseView.prototype.initialize.call(this, arguments);
        },

        render: function(el) {
            el = el || this.el;
            $(el).html(this.template({scope: this}));
            this.$el = $(el);

            this.viewNavigationList = new Common.UI.TreeView({
                el: $('#navigation-list'),
                store: this.storeNavigation,
                enableKeyEvents: false,
                emptyText: this.txtEmpty,
                emptyItemText: this.txtEmptyItem,
                style: 'border: none;',
                delayRenderTips: true,
                minScrollbarLength: 25
            });
            this.viewNavigationList.cmpEl.off('click');
            this.navigationMenu = new Common.UI.Menu({
                items: [{
                        caption     : this.txtPromote,
                        value: 'promote'
                    },
                    {
                        caption     : this.txtDemote,
                        value: 'demote'
                    },
                    {
                        caption     : '--'
                    },
                    {
                        caption     : this.txtHeadingBefore,
                        value: 'before'
                    },
                    {
                        caption     : this.txtHeadingAfter,
                        value: 'after'
                    },
                    {
                        caption     : this.txtNewHeading,
                        value: 'new'
                    },
                    {
                        caption     : '--'
                    },
                    {
                        caption     : this.txtSelect,
                        value: 'select'
                    },
                    {
                        caption     : '--'
                    },
                    {
                        caption     : this.txtExpand,
                        value: 'expand'
                    },
                    {
                        caption     : this.txtCollapse,
                        value: 'collapse'
                    },
                    {
                        caption     : this.txtExpandToLevel,
                        menu: new Common.UI.Menu({
                            menuAlign: 'tl-tr',
                            style: 'min-width: 60px;',
                            items: [{ caption : '1', value: 1 }, { caption : '2', value: 2 }, { caption : '3', value: 3 },
                                { caption : '4', value: 4 }, { caption : '5', value: 5 }, { caption : '6', value: 6 },
                                { caption : '7', value: 7 }, { caption : '8', value: 8 },  { caption : '9', value: 9 }
                            ]
                        })
                    }
                ]
            });

            this.trigger('render:after', this);
            return this;
        },

        show: function () {
            Common.UI.BaseView.prototype.show.call(this,arguments);
            this.fireEvent('show', this );
        },

        hide: function () {
            Common.UI.BaseView.prototype.hide.call(this,arguments);
            this.fireEvent('hide', this );
        },

        ChangeSettings: function(props) {
        },

        txtPromote: 'Promote',
        txtDemote: 'Demote',
        txtHeadingBefore: 'New heading before',
        txtHeadingAfter: 'New heading after',
        txtNewHeading: 'New subheading',
        txtSelect: 'Select content',
        txtExpand: 'Expand all',
        txtCollapse: 'Collapse all',
        txtExpandToLevel: 'Expand to level...',
        txtEmpty: 'There are no headings in the document.
Apply a heading style to the text so that it appears in the table of contents.',
        txtEmptyItem: 'Empty Heading'

    }, DE.Views.Navigation || {}));
});<commit_msg>[DE] Add icons for navigation list<commit_after>/*
 *
 * (c) Copyright Ascensio System SIA 2010-2019
 *
 * This program is a free software product. You can redistribute it and/or
 * modify it under the terms of the GNU Affero General Public License (AGPL)
 * version 3 as published by the Free Software Foundation. In accordance with
 * Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect
 * that Ascensio System SIA expressly excludes the warranty of non-infringement
 * of any third-party rights.
 *
 * This program is distributed WITHOUT ANY WARRANTY; without even the implied
 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR  PURPOSE. For
 * details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
 *
 * You can contact Ascensio System SIA at 20A-12 Ernesta Birznieka-Upisha
 * street, Riga, Latvia, EU, LV-1050.
 *
 * The  interactive user interfaces in modified source and object code versions
 * of the Program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU AGPL version 3.
 *
 * Pursuant to Section 7(b) of the License you must retain the original Product
 * logo when distributing the program. Pursuant to Section 7(e) we decline to
 * grant you any rights under trademark law for use of our trademarks.
 *
 * All the Product's GUI elements, including illustrations and icon sets, as
 * well as technical writing content are licensed under the terms of the
 * Creative Commons Attribution-ShareAlike 4.0 International. See the License
 * terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
 *
*/
/**
 * User: Julia.Radzhabova
 * Date: 14.12.17
 */

define([
    'common/main/lib/util/utils',
    'common/main/lib/component/BaseView',
    'common/main/lib/component/Layout',
    'common/main/lib/component/TreeView'
], function (template) {
    'use strict';

    DE.Views.Navigation = Common.UI.BaseView.extend(_.extend({
        el: '#left-panel-navigation',

        storeNavigation: undefined,
        template: _.template([
            '
',
                // '
<%= scope.strNavigate %>
',
                '
',
                '
',
            '
'
        ].join('')),

        initialize: function(options) {
            _.extend(this, options);
            Common.UI.BaseView.prototype.initialize.call(this, arguments);
        },

        render: function(el) {
            el = el || this.el;
            $(el).html(this.template({scope: this}));
            this.$el = $(el);

            this.viewNavigationList = new Common.UI.TreeView({
                el: $('#navigation-list'),
                store: this.storeNavigation,
                enableKeyEvents: false,
                emptyText: this.txtEmpty,
                emptyItemText: this.txtEmptyItem,
                style: 'border: none;',
                delayRenderTips: true,
                minScrollbarLength: 25
            });
            this.viewNavigationList.cmpEl.off('click');
            this.navigationMenu = new Common.UI.Menu({
                cls: 'shifted-right',
                items: [{
                        iconCls     : 'menu__icon promote',
                        caption     : this.txtPromote,
                        value: 'promote'
                    },
                    {
                        iconCls     : 'menu__icon demote',
                        caption     : this.txtDemote,
                        value: 'demote'
                    },
                    {
                        caption     : '--'
                    },
                    {
                        caption     : this.txtHeadingBefore,
                        value: 'before'
                    },
                    {
                        caption     : this.txtHeadingAfter,
                        value: 'after'
                    },
                    {
                        caption     : this.txtNewHeading,
                        value: 'new'
                    },
                    {
                        caption     : '--'
                    },
                    {
                        iconCls     : 'menu__icon select-all',
                        caption     : this.txtSelect,
                        value: 'select'
                    },
                    {
                        caption     : '--'
                    },
                    {
                        iconCls     : 'menu__icon expand-all',
                        caption     : this.txtExpand,
                        value: 'expand'
                    },
                    {
                        iconCls     : 'menu__icon collapse-all',
                        caption     : this.txtCollapse,
                        value: 'collapse'
                    },
                    {
                        caption     : this.txtExpandToLevel,
                        menu: new Common.UI.Menu({
                            menuAlign: 'tl-tr',
                            style: 'min-width: 60px;',
                            items: [{ caption : '1', value: 1 }, { caption : '2', value: 2 }, { caption : '3', value: 3 },
                                { caption : '4', value: 4 }, { caption : '5', value: 5 }, { caption : '6', value: 6 },
                                { caption : '7', value: 7 }, { caption : '8', value: 8 },  { caption : '9', value: 9 }
                            ]
                        })
                    }
                ]
            });

            this.trigger('render:after', this);
            return this;
        },

        show: function () {
            Common.UI.BaseView.prototype.show.call(this,arguments);
            this.fireEvent('show', this );
        },

        hide: function () {
            Common.UI.BaseView.prototype.hide.call(this,arguments);
            this.fireEvent('hide', this );
        },

        ChangeSettings: function(props) {
        },

        txtPromote: 'Promote',
        txtDemote: 'Demote',
        txtHeadingBefore: 'New heading before',
        txtHeadingAfter: 'New heading after',
        txtNewHeading: 'New subheading',
        txtSelect: 'Select content',
        txtExpand: 'Expand all',
        txtCollapse: 'Collapse all',
        txtExpandToLevel: 'Expand to level...',
        txtEmpty: 'There are no headings in the document.
Apply a heading style to the text so that it appears in the table of contents.',
        txtEmptyItem: 'Empty Heading'

    }, DE.Views.Navigation || {}));
});<|endoftext|>


<commit_before>
var MergeTemplateContainer = require('./data/merge-template-container.js');
var MergeTemplateService = require('./services/merge-template-service.js');
var EmailService = require('./services/email-service.js');
var MergeTemplatesView = require('./views/merge-template/merge-templates-list-view.js');
var CardsView = require('./views/merge-setup/cards-view.js');
var SettingsView = require('./settings/settings-view.js');
var ActionBar = require('./action-bar/action-bar.js');
var Snackbar = require('./snackbar/snackbar.js');
var Dialog = require('./dialog/dialog.js');
var LoadingScreen = require('./loading/loading-screen.js');
var baseHTML = require('./main.html');
var PubSub = require('pubsub-js');

// Handlers
var StandardMailHandler = require('./controllers/standard-mail-handler.js');

var MailMan = function(appendTo) {

  // ***** CONSTANTS ***** //

  //***** LOCAL VARIABLES *****//

  var self = this;
  var actionBar = ActionBar;
  var snackbar = Snackbar;
  var ls = LoadingScreen;

  var mtService = new MergeTemplateService();
  var emailService = new EmailService();
  var templatesContainer;

  var mtListView;
  var cardsView;
  var settingsView;
  var runDialog;
  var deleteDialog;

  // jquery objects
  var base = $(baseHTML);
  var header = base.find('[data-id="header"]');

  //***** PUBLIC *****//

  /**
   * Performs basic set up of the Mailman environment.
   *
   * @constructor
   */
  this.init = function(appendTo) {

    appendTo.append(base);

    actionBar.init(header);
    snackbar.init(base);
    mtListView = new MergeTemplatesView(base);

    settingsView = new SettingsView(base);
    runDialog = new Dialog(appendTo, 'Run this merge?', 'This will run your merge template. ' +
      'Emails will be sent to everyone in your specified sheet. Are you sure you want to merge?');

    deleteDialog = new Dialog(appendTo, 'Delete this merge template?', 'This will remove this merge template. ' +
      'You won\'t be able to send emails using it anymore. Are you sure you want to delete this merge template?');

    // PubSub
    PubSub.subscribe('Rules.delete', function(msg, data) {
      snackbar.show('Merge template deleted.');
    });
    PubSub.subscribe('Rules.add', function(msg, data) {
      snackbar.show('Merge template created.');
    });
    PubSub.subscribe('Rules.update', function(msg, data) {
      snackbar.show('Merge template updated.');
    });
    PubSub.subscribe('Rules.run', function(msg, data) {
      snackbar.show('Running merge "' + data.title + '".');
    });

    PubSub.subscribe('Rules.add', function(msg, data) {
      cardsView.cleanup();
      mtListView.show();

      if (cardsView != null) {
        cardsView.hide();
      }
    });

    PubSub.subscribe('Rules.update', function(msg, data) {
      cardsView.cleanup();
      mtListView.show();

      if (cardsView != null) {
        cardsView.hide();
      }
    });

    actionBar.setSettingsHandler(function() {
      settingsView.show();
      mtListView.hide();

      if (cardsView != null) {
        cardsView.hide();
      }
    });

    mtListView.setInstantHandler(function(e) {
      cardsView = createCardsView(base, StandardMailHandler);

      mtListView.hide();
      cardsView.show();
    });

    mtListView.setDeleteHandler(function(template) {
      deleteDialog.show().then(
        function() {
          // This only occurs when the user clicks OK.
          templatesContainer.remove(template);
        },
        function(err) {
          console.error(err);
        }
      ).done();
    });

    mtListView.setEditHandler(function(template) { // TODO
      cardsView = createCardsView(base, StandardMailHandler, template);
      mtListView.hide();
      cardsView.show();
    });

    mtListView.setRunHandler(function(template) {
      runDialog.show()
        .then(function() {
          // This only occurs when the user clicks OK.
          emailService.send(template);
          PubSub.publish('Rules.run', template); // TODO
        }).done();
    });

    mtService.getAll().then(
      function(result) {
        templatesContainer = new MergeTemplateContainer(result);
        mtListView.setContainer(templatesContainer);
        ls.hide();
      },
      function(err) {
        console.error(err);
      }
    ).done();

    mtListView.show();
  };

  //***** PRIVATE *****//

  var createCardsView = function(base, handler, data) {

    var view = new CardsView(base, handler, data);

    view.done().then(
      function(template) {
        if (templatesContainer.indexOf(template.toConfig().id) !== -1) {
          templatesContainer.update(template);
        }
        else {
          templatesContainer.add(template.toConfig());
        }
      },
      function() {
        view.cleanup();

        mtListView.show();
        view.hide();
      }
    ).done();

    return view;
  };

  this.init(appendTo);
};

/**  */
module.exports = MailMan;
<commit_msg>Updated the ActionBar notification for when the run button is clicked.<commit_after>
var MergeTemplateContainer = require('./data/merge-template-container.js');
var MergeTemplateService = require('./services/merge-template-service.js');
var EmailService = require('./services/email-service.js');
var MergeTemplatesView = require('./views/merge-template/merge-templates-list-view.js');
var CardsView = require('./views/merge-setup/cards-view.js');
var SettingsView = require('./settings/settings-view.js');
var ActionBar = require('./action-bar/action-bar.js');
var Snackbar = require('./snackbar/snackbar.js');
var Dialog = require('./dialog/dialog.js');
var LoadingScreen = require('./loading/loading-screen.js');
var baseHTML = require('./main.html');
var PubSub = require('pubsub-js');

// Handlers
var StandardMailHandler = require('./controllers/standard-mail-handler.js');

var MailMan = function(appendTo) {

  // ***** CONSTANTS ***** //

  //***** LOCAL VARIABLES *****//

  var self = this;
  var actionBar = ActionBar;
  var snackbar = Snackbar;
  var ls = LoadingScreen;

  var mtService = new MergeTemplateService();
  var emailService = new EmailService();
  var templatesContainer;

  var mtListView;
  var cardsView;
  var settingsView;
  var runDialog;
  var deleteDialog;

  // jquery objects
  var base = $(baseHTML);
  var header = base.find('[data-id="header"]');

  //***** PUBLIC *****//

  /**
   * Performs basic set up of the Mailman environment.
   *
   * @constructor
   */
  this.init = function(appendTo) {

    appendTo.append(base);

    actionBar.init(header);
    snackbar.init(base);
    mtListView = new MergeTemplatesView(base);

    settingsView = new SettingsView(base);
    runDialog = new Dialog(appendTo, 'Run this merge?', 'This will run your merge template. ' +
      'Emails will be sent to everyone in your specified sheet. Are you sure you want to merge?');

    deleteDialog = new Dialog(appendTo, 'Delete this merge template?', 'This will remove this merge template. ' +
      'You won\'t be able to send emails using it anymore. Are you sure you want to delete this merge template?');

    // PubSub
    PubSub.subscribe('Rules.delete', function(msg, data) {
      snackbar.show('Merge template deleted.');
    });
    PubSub.subscribe('Rules.add', function(msg, data) {
      snackbar.show('Merge template created.');
    });
    PubSub.subscribe('Rules.update', function(msg, data) {
      snackbar.show('Merge template updated.');
    });
    PubSub.subscribe('Rules.run', function(msg, data) {
      snackbar.show('Running merge "' + data.mergeData.title + '".');
    });

    PubSub.subscribe('Rules.add', function(msg, data) {
      cardsView.cleanup();
      mtListView.show();

      if (cardsView != null) {
        cardsView.hide();
      }
    });

    PubSub.subscribe('Rules.update', function(msg, data) {
      cardsView.cleanup();
      mtListView.show();

      if (cardsView != null) {
        cardsView.hide();
      }
    });

    actionBar.setSettingsHandler(function() {
      settingsView.show();
      mtListView.hide();

      if (cardsView != null) {
        cardsView.hide();
      }
    });

    mtListView.setInstantHandler(function(e) {
      cardsView = createCardsView(base, StandardMailHandler);

      mtListView.hide();
      cardsView.show();
    });

    mtListView.setDeleteHandler(function(template) {
      deleteDialog.show().then(
        function() {
          // This only occurs when the user clicks OK.
          templatesContainer.remove(template);
        },
        function(err) {
          console.error(err);
        }
      ).done();
    });

    mtListView.setEditHandler(function(template) { // TODO
      cardsView = createCardsView(base, StandardMailHandler, template);
      mtListView.hide();
      cardsView.show();
    });

    mtListView.setRunHandler(function(template) {
      runDialog.show()
        .then(function() {
          // This only occurs when the user clicks OK.
          emailService.send(template);
          PubSub.publish('Rules.run', template.toConfig()); // TODO
        }).done();
    });

    mtService.getAll().then(
      function(result) {
        templatesContainer = new MergeTemplateContainer(result);
        mtListView.setContainer(templatesContainer);
        ls.hide();
      },
      function(err) {
        console.error(err);
      }
    ).done();

    mtListView.show();
  };

  //***** PRIVATE *****//

  var createCardsView = function(base, handler, data) {

    var view = new CardsView(base, handler, data);

    view.done().then(
      function(template) {
        if (templatesContainer.indexOf(template.toConfig().id) !== -1) {
          templatesContainer.update(template);
        }
        else {
          templatesContainer.add(template.toConfig());
        }
      },
      function() {
        view.cleanup();

        mtListView.show();
        view.hide();
      }
    ).done();

    return view;
  };

  this.init(appendTo);
};

/**  */
module.exports = MailMan;
<|endoftext|>


<commit_before>"use strict";
var secrets = require('../secrets.json');
var express = require('express');
var router = express.Router();
var request = require('request');
var common = require('../services/common.js');

router.all(['/database', '/database/*'], function(req, res, next) {
    if(!common.isAdmin(req.session.user && req.session.user.email)) return next();
    req.pipe(request(getUrl(req.url))).pipe(res);
});

function getUrl(url) {
    //Path.join is not working with request.pipe. So this hack for removing the extra / in the secrets.db
    return secrets.db.replace(/\/$/, '') + url.replace(/^\/database/, '');
}

module.exports = router;<commit_msg>Bug fix<commit_after>"use strict";
var secrets = require('../secrets.json');
var express = require('express');
var router = express.Router();
var request = require('request');
var common = require('../services/common.js');

router.all(['/_utils', '/_utils/*'], function(req, res, next) {
    if(!common.isAdmin(req.session.user && req.session.user.email)) return next();
    req.pipe(request(getUrl(req.url))).pipe(res);
});

function getUrl(url) {
    //Path.join is not working with request.pipe. So this hack for removing the extra / in the secrets.db
    return secrets.db.replace(/\/$/, '') + url.replace(/^\/database/, '');
}

module.exports = router;<|endoftext|>


<commit_before>'use strict';

const Fs = require('fs');

const Hoek = require('hoek');
const Thurston = require('thurston');
const Items = require('items');
const Some = require('lodash.some');

const internals = {
    defaults: {
        pattern: ['bytes', 'filename', 'headers', 'path']
    }
};

internals.validate = function (payload, cache) {

    const keys = internals.defaults.pattern;

    return function (item, next) {

        if (!(Hoek.contain(payload[item], keys))) {
            return next();
        }

        cache[item] = Fs.createReadStream(payload[item].path, { start: 0, end: 2 });

        next();
    };
};

exports.validate = function (payload, options, next) {

    const keys = internals.defaults.pattern;

    if (!Some(payload, (v) => Hoek.contain(v, keys))) {
        return next();
    }

    const items = Object.keys(payload);
    const res = {};
    const iterator = internals.validate(payload, res);

    Items.serial(items, iterator, () => {

        Thurston.validate(res, options, (err) => {

            if (err) {
                return next(err);
            }

            return next(null, payload);
        });
    });
};
<commit_msg>Fix some code style idiosyncrasies<commit_after>'use strict';

const Fs = require('fs');

const Hoek = require('hoek');
const Thurston = require('thurston');
const Items = require('items');
const Some = require('lodash.some');

const internals = {
    defaults: {
        pattern: ['bytes', 'filename', 'headers', 'path']
    }
};

internals.validate = function (payload, cache) {

    const keys = internals.defaults.pattern;

    return function (item, next) {

        if (!(Hoek.contain(payload[item], keys))) {
            return next();
        }

        cache[item] = Fs.createReadStream(payload[item].path, { start: 0, end: 2 });

        next();
    };
};

exports.validate = function (payload, options, next) {

    const keys = internals.defaults.pattern;

    if (!Some(payload, (v) => Hoek.contain(v, keys))) {
        return next();
    }

    const items = Object.keys(payload);
    const res = {};
    const iterator = internals.validate(payload, res);

    Items.serial(items, iterator, () => {

        Thurston.validate(res, options, (err) => {

            if (err) {
                return next(err);
            }

            next(null, payload);
        });
    });
};
<|endoftext|>


<commit_before>"use strict";
/* eslint new-cap: 0 */

var express = require('express');
var router = express.Router();

var getFeatureFilePaths = require("../lib/specifications/getFeatureFilePaths");
var getFeatureFile = require("../lib/specifications/getFeatureFile");

// TODO post a new repo from which to extract features.

// Default route for 'features' is the list of available features.
router.get('/', function(req, res) {
  getFeatureFilePaths('public/feature-files')
    .then(function(featureFilePaths) {
      featureFilePaths = featureFilePaths.map(function(featurePath) {

        // Map from the storage directory to the Express route.
        // TODO move inside getFeatureFilePaths and call it getFeatureFileRoutes.
        featurePath = featurePath.replace('public/feature-files/', 'features/');

        // Create a display name for the feature.
        // TODO move inside getFeatureFilePaths and call it getFeatureFileRoutes.
        var featureName = featurePath.replace('.feature', '').replace('features/', '');

        return {
          featurePath: featurePath,
          featureName: featureName
        };
      });

      res.render('features', {paths: featureFilePaths});
    })
    .catch(function(err) {
      res
        .status(500)
        .send(err);
    });
});

// Match all routes with something after the slash
// and display an individual feature.
router.get(/^\/(.+)/, function(req, res) {
  var featureFilePath = req.params[0];

  // Map from Express route to file system directory.
  // TODO move into getFeature function.
  featureFilePath = featureFilePath.replace('/features/', '/public/feature-files/');

  getFeatureFile(featureFilePath)
    .then(function(fileContents) {

      // Parse the feature from the file contents.
      // TODO move to a separate module.
      var featureLines = fileContents.split('\n');
      res.render('feature', {featureLines: featureLines});
    })
    .catch(function(err) {
      res
        .status(err.code === 'ENOENT' ? 404 : 500)
        .send(err);
    });
})

module.exports = router;
<commit_msg>Remove a string modification that in most cases wasn't doing anything and in some cases was breaking things.<commit_after>"use strict";
/* eslint new-cap: 0 */

var express = require('express');
var router = express.Router();

var getFeatureFilePaths = require("../lib/specifications/getFeatureFilePaths");
var getFeatureFile = require("../lib/specifications/getFeatureFile");

// TODO post a new repo from which to extract features.

// Default route for 'features' is the list of available features.
router.get('/', function(req, res) {
  getFeatureFilePaths('public/feature-files')
    .then(function(featureFilePaths) {
      featureFilePaths = featureFilePaths.map(function(featurePath) {

        // Map from the storage directory to the Express route.
        // TODO move inside getFeatureFilePaths and call it getFeatureFileRoutes.
        featurePath = featurePath.replace('public/feature-files/', 'features/');

        // Create a display name for the feature.
        // TODO move inside getFeatureFilePaths and call it getFeatureFileRoutes.
        var featureName = featurePath.replace('.feature', '').replace('features/', '');

        return {
          featurePath: featurePath,
          featureName: featureName
        };
      });

      res.render('features', {paths: featureFilePaths});
    })
    .catch(function(err) {
      res
        .status(500)
        .send(err);
    });
});

// Match all routes with something after the slash
// and display an individual feature.
router.get(/^\/(.+)/, function(req, res) {
  var featureFilePath = req.params[0];

  getFeatureFile(featureFilePath)
    .then(function(fileContents) {

      // Parse the feature from the file contents.
      // TODO move to a separate module.
      var featureLines = fileContents.split('\n');
      res.render('feature', {featureLines: featureLines});
    })
    .catch(function(err) {
      res
        .status(err.code === 'ENOENT' ? 404 : 500)
        .send(err);
    });
})

module.exports = router;
<|endoftext|>


<commit_before>const _ = require('lodash');
const moment = require('moment');
const Promise = require('bluebird');
const debug = require('debug')('routes:personal');
const nconf = require('nconf');

const mongo = require('../lib/mongo');
const params = require('../lib/params');
const utils = require('../lib/utils');
const adopters = require('../lib/adopters');
const produceCSVv1 = require('../lib/CSV');

function summary(req) {
    const { amount, skip } = params.optionParsing(req.params.paging, 200);
    debug("summary request, amount %d skip %d", amount, skip);

    return adopters
        .validateToken(req.params.userToken)
        .then(function(supporter) {
            return mongo
                .readLimit(nconf.get('schema').summary, { user: supporter.pseudo },
                    { impressionTime: -1}, amount, skip);
        })
        .map(function(e) {
            return _.omit(e, ['_id' ]);
        })
        .then(function(data) {
            debug("retrived %d objects, with amount %d skip %d", _.size(data), amount, skip);
            return { json: data };
        })
        .catch(function(e) {
            debug("data (error): %s", e);
            return { 'text': `error: ${e}` };
        });
};

function personalCSV(req) {
    const { amount, skip } = params.optionParsing(req.params.paging, 1000);
    debug("CSV request by [%s], amount %d skip %d", req.params.userToken, amount, skip);

    return adopters
        .validateToken(req.params.userToken)
        .then(function(supporter) {
            return mongo
                .readLimit(nconf.get('schema').summary, { user: supporter.pseudo },
                    { impressionTime: -1}, amount, skip);
        })
        .tap(function(check) {
            if(!_.size(check)) throw new Error("Invalid token");
        })
        .then(produceCSVv1)
        .then(function(structured) {
            debug("personalCSV produced %d bytes", _.size(structured));
            const fname=`summary-${skip}-${amount}.csv`;
            return {
                headers: { "Content-Type": "csv/text",
                           "content-disposition": `attachment; filename=${fname}` },
                text: structured,
            };
        })
        .catch(function(e) {
            debug("csv (error): %s", e);
            return { text: `error: ${e}` };
        });
}

function metadata(req) {
    const { amount, skip } = params.optionParsing(req.params.paging);
    debug("metadata request: %d skip %d", amount, skip);
    return adopters
        .validateToken(req.params.userToken)
        .then(function(supporter) {
            return mongo
                .readLimit(nconf.get('schema').metadata, { userId: supporter.userId },
                    { impressionTime: -1}, amount, skip);
        })
        .then(function(data) {
            debug("retrived %d objects, with amount %d skip %d", _.size(data), amount, skip);
            return { json: data };
        })
        .catch(function(e) {
            debug("data (error): %s", e);
            return { 'text': `error: ${e}` };
        });
};

function enrich(req) {

    var amount = 0, skip, pipeline, when = null;
    if(!req.params.paging || _.size(_.split(req.params.paging, '-')) == 2) {
        let paging = params.optionParsing(req.params.paging);
        amount = paging.amount;
        skip = paging.skip;
        debug("enrich with paging, looking for %d, skip %d", amount, skip);
    }
    else {
        when = moment(req.params.paging);
        debug("enrich by day: looking for %s", when);
    }

    if( (when != null && !when.isValid()) || (when == null && !amount) )
        throw new Error("Invalid parameter: $amount-$skip OR $(year-$month-day)")

    /* pipeline should be:
            match, limit, sort, lookup
       the match and limits are added below in the promise chain */
    let lookup = [
        { $sort: { impressionTime: -1 } },
        { $lookup: {
            from: 'labels',
            localField: 'semanticId',
            foreignField: 'semanticId',
            as: 'labelcopy'
        }}
    ];

    return adopters
        .validateToken(req.params.userToken)
        .then(function(supporter) {

            if(when) {
                const startOf = new Date(when.startOf('day').toISOString());
                const endOf = new Date(when.add(1, 'd').startOf('day').toISOString());
                pipeline = _.concat([
                    { $match: {
                        user: supporter.pseudo,
                        impressionTime: { $lt: endOf, $gt: startOf }
                    } }
                ], lookup);
            } else {
                pipeline = _.concat([
                    { $match: { user: supporter.pseudo } },
                    { $skip: skip },
                    { $limit: amount }
                ], lookup);
            }

            return mongo
                .aggregate(nconf.get('schema').summary, pipeline);
        })
        .map(function(e) {
            if(_.size(e.labelcopy)) {
                e.labels = _.get(e.labelcopy[0], 'l');
                e.lang = _.get(e.labelcopy[0], 'lang');
            }
            return _.omit(e, ['_id', 'id', 'labelcopy' ]);
        })
        .then(function(prod) {
            return { json: prod };
        });
};

function byTimelineLookup(userId, amount, skipnum) {
    /* let me intrduce you to the bigger pipeline of this code iteration */

    const match = { $match: {userId: userId }};
    const sort = { $sort: { startTime: -1 }};
    const skip = { $skip: skipnum };
    const limit = { $limit: amount };
    const lookup = { $lookup: { from: 'impressions2', localField: 'id', foreignField: 'timelineId', as: 'impressions'}};
    const unwind = { $unwind: { path: "$impressions", preserveNullAndEmptyArrays: true } };
    const project = { $project: {
        _id: 0,
        "impressionOrder": "$impressions.impressionOrder",
        "impressionTime": "$impressions.impressionTime",
        "htmlId": "$impressions.htmlId",
        geoip: 1,
        startTime: 1,
        "timelineId": "$id"
    }};
    const summaryl = { $lookup: { from: 'summary', localField: 'htmlId', foreignField: 'id', as: 'summary'  }};

    return mongo
        .aggregate(nconf.get('schema').timelines, [ match, sort, skip, limit, lookup, unwind, project, summaryl ]);
};

function stats(req) {

    const DEFTIMLN = 20;
    const { amount, skip } = params.optionParsing(req.params.paging, DEFTIMLN);

    debug("Personal statistics requested, it process timelines: amount %d skip %d",
        amount, skip);

    return adopters
        .validateToken(req.params.userToken)
        .then(function(supporter) {
            return Promise.all([
                mongo.count(nconf.get('schema').timelines, { userId: supporter.userId }),
                byTimelineLookup(supporter.userId, amount, skip)
            ])
        })
        .then(function(result) {
            const timelines = _.countBy(result[1], 'timelineId')
            debug("Retrieved %d impressions in %d timelines, first %s, last %s",
                _.size(result[1]), _.size(timelines), _.first(result[1]).impressionTime, _.last(result[1]).impressionTime);

            /* order the timeline by startTime and impressionOrder */
            const orderedTimelines = _.map(_.map(_.groupBy(result[1], 'timelineId'), _.first), 'startTime');
            let sorted = [];
            _.each(_.orderBy(orderedTimelines, new Date), function(sT) {
                sorted = _.concat(sorted, _.orderBy(_.filter(result[1], { startTime: sT }), 'impressionOrder'));
            });

            return {
                json: {
                    storedTimelines: result[0],
                    served: { amount, skip },
                    content: sorted,
                    timelines,
                }
            };
        });

};

function estimateDuration(impressions) {
    const f = _.first(impressions);
    const l = _.last(impressions);

    if(!f || !l || l.id == f.id)
        return 0;

    return moment.duration(
        moment(l.impressionTime) - moment(f.impressionTime)
    ).asSeconds();
};

function paginateDay(amount, skip) {
  // default is: 3, 0
  // minimum day is: today at 00:00 minus three day
  // maximum day is: this midnight

    const maxday = moment().startOf('day').add(1, 'd').subtract(skip, 'd');
    const minday = moment().startOf('day').add(1, 'd').subtract(amount + 1, 'd').subtract(skip, 'd');

    debug("%d-%d %j", amount, skip, {minday, maxday});
    return { minday, maxday };
}

function daily(req) {
    const { amount, skip } = params.optionParsing(req.params.paging, 3);
    const dayamount = ( amount < 3 ) ? 3 : amount;

    const { minday, maxday } = paginateDay(amount, skip);

    debug("Personal daily statistics day ago %d, day amount %d - range %s %s",
        skip, dayamount,
        minday.format("YYYY-MM-DD"), maxday.format("YYYY-MM-DD"));

    return adopters
        .validateToken(req.params.userToken)
        .then(function(supporter) {
            return mongo.aggregate(nconf.get('schema').timelines, [
                { $match: {
                    userId: supporter.userId,
                    startTime: { $gt: new Date(minday.toISOString()), $lt: new Date(maxday.toISOString()) }
                }},
                { $group: { _id: {
                     year:  { $year: "$startTime" },
                     month: { $month: "$startTime" },
                     day:   { $dayOfMonth: "$startTime" },
                     userId: "$userId"
                   },
                   day: { $first: "$startTime" },
                   ids: { $addToSet: "$id" },
                 }},
                 { $project: { userId: "$_id.userId", count: "$_id.count", day: true, "timelineId": "$ids", _id: false }},
                 { $sort: { days: -1 }},
                 { $unwind: "$timelineId" },
                 { $lookup: { from: 'metadata', localField: 'timelineId', foreignField: 'timelineId', as: 'metadata'}},
                 { $lookup: { from: 'impressions2', localField: 'timelineId', foreignField: 'timelineId', as: 'impressions'}}
            ]);
        })
        .map(function(timeline) {
            timeline.dayString = moment(timeline.day).format("YYYY-MM-DD");
            timeline.durationSeconds = estimateDuration(timeline.impressions);
            return timeline;
        })
        .then(function(x) {
            return _.values(_.groupBy(x, 'dayString'));
        })
        .map(function(dayr) {
            // refactor: at events the timeline is saved pseudonymized and this lead
            //           to query summary this would make simpler the sums below.
            const metadatas = _.flatten(_.map(dayr, 'metadata'));
            const ntimelines = _.size(dayr);
            const npost = _.size(metadatas);
            const nature = _.countBy(metadatas, 'nature');
            const totalSeconds = _.sum(_.map(dayr, 'durationSeconds'));
            const duration = moment.duration({ seconds: totalSeconds }).humanize();
            const sources = _.size(_.uniq(_.map(metadatas, function(m) {
                return m.attributions[0].content;
            })));

            return {
                day: dayr[0].dayString,
                ntimelines,
                npost,
                nature,
                totalSeconds,
                duration,
                sources,
            };
        })
        .then(function(stats) {
            debug("Computed daily stats for %d %j %j",
                _.size(stats), _.map(stats, 'duration'), _.map(stats, 'npost'));
            return { json: stats };
        });
};

module.exports = {
    summary,
    metadata,
    personalCSV,
    enrich,
    daily,
    stats
};
<commit_msg>addressing and trimming #142<commit_after>const _ = require('lodash');
const moment = require('moment');
const Promise = require('bluebird');
const debug = require('debug')('routes:personal');
const nconf = require('nconf');

const mongo = require('../lib/mongo');
const params = require('../lib/params');
const utils = require('../lib/utils');
const adopters = require('../lib/adopters');
const produceCSVv1 = require('../lib/CSV');

function summary(req) {
    const { amount, skip } = params.optionParsing(req.params.paging, 200);
    debug("summary request, amount %d skip %d", amount, skip);

    return adopters
        .validateToken(req.params.userToken)
        .then(function(supporter) {
            return mongo
                .readLimit(nconf.get('schema').summary, { user: supporter.pseudo },
                    { impressionTime: -1}, amount, skip);
        })
        .map(function(e) {
            return _.omit(e, ['_id' ]);
        })
        .then(function(data) {
            debug("retrived %d objects, with amount %d skip %d", _.size(data), amount, skip);
            return { json: data };
        })
        .catch(function(e) {
            debug("data (error): %s", e);
            return { 'text': `error: ${e}` };
        });
};

function personalCSV(req) {
    const { amount, skip } = params.optionParsing(req.params.paging, 1000);
    debug("CSV request by [%s], amount %d skip %d", req.params.userToken, amount, skip);

    return adopters
        .validateToken(req.params.userToken)
        .then(function(supporter) {
            return mongo
                .readLimit(nconf.get('schema').summary, { user: supporter.pseudo },
                    { impressionTime: -1}, amount, skip);
        })
        .tap(function(check) {
            if(!_.size(check)) throw new Error("Invalid token");
        })
        .then(produceCSVv1)
        .then(function(structured) {
            debug("personalCSV produced %d bytes", _.size(structured));
            const fname=`summary-${skip}-${amount}.csv`;
            return {
                headers: { "Content-Type": "csv/text",
                           "content-disposition": `attachment; filename=${fname}` },
                text: structured,
            };
        })
        .catch(function(e) {
            debug("csv (error): %s", e);
            return { text: `error: ${e}` };
        });
}

function metadata(req) {
    const { amount, skip } = params.optionParsing(req.params.paging);
    debug("metadata request: %d skip %d", amount, skip);
    return adopters
        .validateToken(req.params.userToken)
        .then(function(supporter) {
            return mongo
                .readLimit(nconf.get('schema').metadata, { userId: supporter.userId },
                    { impressionTime: -1}, amount, skip);
        })
        .then(function(data) {
            debug("retrived %d objects, with amount %d skip %d", _.size(data), amount, skip);
            return { json: data };
        })
        .catch(function(e) {
            debug("data (error): %s", e);
            return { 'text': `error: ${e}` };
        });
};

function enrich(req) {

    var amount = 0, skip, pipeline, when = null;
    if(!req.params.paging || _.size(_.split(req.params.paging, '-')) == 2) {
        let paging = params.optionParsing(req.params.paging);
        amount = paging.amount;
        skip = paging.skip;
        debug("enrich with paging, looking for %d, skip %d", amount, skip);
    }
    else {
        when = moment(req.params.paging);
        debug("enrich by day: looking for %s", when);
    }

    if( (when != null && !when.isValid()) || (when == null && !amount) )
        throw new Error("Invalid parameter: $amount-$skip OR $(year-$month-day)")

    /* pipeline should be:
            match, limit, sort, lookup
       the match and limits are added below in the promise chain */
    let lookup = [
        { $sort: { impressionTime: -1 } },
        { $lookup: {
            from: 'labels',
            localField: 'semanticId',
            foreignField: 'semanticId',
            as: 'labelcopy'
        }}
    ];

    return adopters
        .validateToken(req.params.userToken)
        .then(function(supporter) {

            if(when) {
                const startOf = new Date(when.startOf('day').toISOString());
                const endOf = new Date(when.add(1, 'd').startOf('day').toISOString());
                pipeline = _.concat([
                    { $match: {
                        user: supporter.pseudo,
                        impressionTime: { $lt: endOf, $gt: startOf }
                    } }
                ], lookup);
            } else {
                pipeline = _.concat([
                    { $match: { user: supporter.pseudo } },
                    { $skip: skip },
                    { $limit: amount }
                ], lookup);
            }

            return mongo
                .aggregate(nconf.get('schema').summary, pipeline);
        })
        .map(function(e) {
            if(_.size(e.labelcopy)) {
                e.labels = _.get(e.labelcopy[0], 'l');
                e.lang = _.get(e.labelcopy[0], 'lang');
            }
            return _.omit(e, ['_id', 'id', 'labelcopy' ]);
        })
        .then(function(prod) {
            return { json: prod };
        });
};

function byTimelineLookup(userId, amount, skipnum) {
    /* let me intrduce you to the bigger pipeline of this code iteration */

    const match = { $match: {userId: userId }};
    const sort = { $sort: { startTime: -1 }};
    const skip = { $skip: skipnum };
    const limit = { $limit: amount };
    const lookup = { $lookup: { from: 'impressions2', localField: 'id', foreignField: 'timelineId', as: 'impressions'}};
    const unwind = { $unwind: { path: "$impressions", preserveNullAndEmptyArrays: true } };
    const project = { $project: {
        _id: 0,
        "impressionOrder": "$impressions.impressionOrder",
        "impressionTime": "$impressions.impressionTime",
        "htmlId": "$impressions.htmlId",
        geoip: 1,
        startTime: 1,
        "timelineId": "$id"
    }};
    const summaryl = { $lookup: { from: 'summary', localField: 'htmlId', foreignField: 'id', as: 'summary'  }};

    return mongo
        .aggregate(nconf.get('schema').timelines, [ match, sort, skip, limit, lookup, unwind, project, summaryl ]);
};

function stats(req) {

    const DEFTIMLN = 20;
    const { amount, skip } = params.optionParsing(req.params.paging, DEFTIMLN);

    debug("Personal statistics requested, it process timelines: amount %d skip %d",
        amount, skip);

    return adopters
        .validateToken(req.params.userToken)
        .then(function(supporter) {
            return Promise.all([
                mongo.count(nconf.get('schema').timelines, { userId: supporter.userId }),
                byTimelineLookup(supporter.userId, amount, skip)
            ])
        })
        .then(function(result) {
            const timelines = _.countBy(result[1], 'timelineId')
            debug("Retrieved %d impressions in %d timelines, first %s, last %s",
                _.size(result[1]), _.size(timelines), _.first(result[1]).impressionTime, _.last(result[1]).impressionTime);

            /* order the timeline by startTime and impressionOrder */
            const orderedTimelines = _.map(_.map(_.groupBy(result[1], 'timelineId'), _.first), 'startTime');
            let sorted = [];
            _.each(_.orderBy(orderedTimelines, new Date), function(sT) {
                sorted = _.concat(sorted, _.orderBy(_.filter(result[1], { startTime: sT }), 'impressionOrder'));
            });

            return {
                json: {
                    storedTimelines: result[0],
                    served: { amount, skip },
                    content: sorted,
                    timelines,
                }
            };
        });

};

function estimateDuration(impressions) {
    const f = _.first(impressions);
    const l = _.last(impressions);

    if(!f || !l || l.id == f.id)
        return 0;

    return moment.duration(
        moment(l.impressionTime) - moment(f.impressionTime)
    ).asSeconds();
};

function daily(req) {
    const { amount, skip } = params.optionParsing(req.params.paging, 3);
    const dayamount = ( amount < 3 ) ? 3 : amount;

    const minday = moment()
        .startOf('day')
        .add(1, 'd')
        .subtract(amount + 1, 'd')
        .subtract(skip, 'd');

    const maxday = moment()
        .startOf('day')
        .add(1, 'd')
        .subtract(skip, 'd');

    debug("Personal daily statistics day ago %d, day amount %d - range %s %s",
        skip, dayamount,
        minday.format("YYYY-MM-DD"), maxday.format("YYYY-MM-DD"));

    return adopters
        .validateToken(req.params.userToken)
        .then(function(supporter) {
            return mongo.aggregate(nconf.get('schema').timelines, [
                { $match: {
                    userId: supporter.userId,
                    startTime: { $gt: new Date(minday.toISOString()), $lt: new Date(maxday.toISOString()) }
                }},
                { $group: { _id: {
                     year:  { $year: "$startTime" },
                     month: { $month: "$startTime" },
                     day:   { $dayOfMonth: "$startTime" },
                     userId: "$userId"
                   },
                   day: { $first: "$startTime" },
                   ids: { $addToSet: "$id" },
                 }},
                 { $project: { userId: "$_id.userId", count: "$_id.count", day: true, "timelineId": "$ids", _id: false }},
                 { $sort: { day: -1 }},
                 { $unwind: "$timelineId" },
                 { $lookup: { from: 'metadata', localField: 'timelineId', foreignField: 'timelineId', as: 'metadata'}},
                 { $lookup: { from: 'impressions2', localField: 'timelineId', foreignField: 'timelineId', as: 'impressions'}}
            ]);
        })
        .map(function(timeline) {
            timeline.dayString = moment(timeline.day).format("YYYY-MM-DD");
            timeline.durationSeconds = estimateDuration(timeline.impressions);
            return timeline;
        })
        .then(function(x) {
            return _.values(_.groupBy(x, 'dayString'));
        })
        .map(function(dayr) {
            // refactor: at events the timeline is saved pseudonymized and this lead
            //           to query summary this would make simpler the sums below.
            const metadatas = _.flatten(_.map(dayr, 'metadata'));
            const ntimelines = _.size(dayr);
            const npost = _.size(metadatas);
            const nature = _.countBy(metadatas, 'nature');
            const totalSeconds = _.sum(_.map(dayr, 'durationSeconds'));
            const duration = moment.duration({ seconds: totalSeconds }).humanize();
            const sources = _.size(_.uniq(_.map(metadatas, function(m) {
                return m.attributions[0].content;
            })));

            return {
                day: dayr[0].dayString,
                ntimelines,
                npost,
                nature,
                totalSeconds,
                duration,
                sources,
            };
        })
        .then(function(stats) {
            debug("Computed daily stats for %d %j %j",
                _.size(stats), _.map(stats, 'duration'), _.map(stats, 'npost'));
            return { json: stats };
        });
};

module.exports = {
    summary,
    metadata,
    personalCSV,
    enrich,
    daily,
    stats
};
<|endoftext|>


<commit_before>
/*!
 * CSS - Lexer
 * Copyright(c) 2010 LearnBoost <dev@learnboost.com>
 * MIT Licensed
 */

/**
 * Module dependencies.
 */

var Token = require('./token')
  , nodes = require('./nodes');

/**
 * Operator aliases.
 */

var alias = {
    and: '&&'
  , or: '||'
};

/**
 * Numeric strings used for exceptions.
 */

var numberString = [, 'one', 'two'];

/**
 * Units.
 */

var units = [
    'em'
  , 'ex'
  , 'px'
  , 'mm'
  , 'cm'
  , 'in'
  , 'pt'
  , 'pc'
  , 'deg'
  , 'rad'
  , 'grad'
  , 'ms'
  , 's'
  , 'Hz'
  , 'kHz'
  , '%'].join('|');

/**
 * Unit RegExp.
 */

var unit = new RegExp('^(-)?(\\d+\\.\\d+|\\d+|\\.\\d+)(' + units + ')? *');

/**
 * Initialize a new `Lexer` with the given `str` and `options`.
 *
 * @param {String} str
 * @param {Object} options
 * @api private
 */

var Lexer = module.exports = function Lexer(str, options) {
  options = options || {};
  this.str = str.replace(/\r\n?/g, '\n').replace(/\t/g, '  ');
  this.stash = [];
  this.prevIndents = 0;
  this.lineno = 0;
};

/**
 * Lexer prototype.
 */

Lexer.prototype = {

  /**
   * Custom inspect.
   */

  inspect: function(){
    var tok
      , tmp = this.str
      , buf = [];
    while ('eos' != (tok = this.next).type) {
      buf.push(tok.inspect());
    }
    this.str = tmp;
    this.prevIndents = 0;
    return buf.concat(tok.inspect()).join('\n');
  },

  /**
   * Lookahead `n` tokens.
   *
   * @param {Number} n
   * @return {Object}
   * @api private
   */

  lookahead: function(n){
    var fetch = n - this.stash.length;
    while (fetch-- > 0) this.stash.push(this.advance);
    return this.stash[--n];
  },

  /**
   * Consume the given `len`.
   *
   * @param {Number|Array} len
   * @api private
   */

  skip: function(len){
    this.str = this.str.substr(Array.isArray(len)
      ? len[0].length
      : len);
  },

  /**
   * Fetch next token including those stashed by peek.
   *
   * @return {Token}
   * @api private
   */

  get next() {
    var tok = this.stashed || this.advance;
    switch (tok.type) {
      case 'newline':
      case 'selector':
      case 'indent':
        ++this.lineno;
    }
    tok.lineno = this.lineno;
    return tok;
  },

  /**
   * Fetch next token.
   *
   * @return {Token}
   * @api private
   */

  get advance() {
    return this.eos
      || this.null
      || this.keyword
      || this.atrule
      || this.media
      || this.comment
      || this.newline
      || this.escaped
      || this.important
      || this.literal
      || this.function
      || this.brace
      || this.paren
      || this.color
      || this.string
      || this.unit
      || this.namedop
      || this.boolean
      || this.ident
      || this.op
      || this.space
      || this.selector;
  },

  /**
   * Lookahead a single token.
   *
   * @return {Token}
   * @api private
   */

  get peek() {
    return this.lookahead(1);
  },

  /**
   * Return the next possibly stashed token.
   *
   * @return {Token}
   * @api private
   */

  get stashed() {
    return this.stash.shift();
  },

  /**
   * EOS | trailing outdents.
   */

  get eos() {
    if (this.str.length) return;
    return --this.prevIndents < 0
      ? new Token('eos')
      : new Token('outdent');
  },

  /**
   * ' '+
   */

  get space() {
    var captures;
    if (captures = /^( +)/.exec(this.str)) {
      this.skip(captures);
      return new Token('space');
    }
  },

  /**
   * '\\' . ' '*
   */

  get escaped() {
    var captures;
    if (captures = /^\\(.) */.exec(this.str)) {
      var c = captures[1];
      this.skip(captures);
      return new Token('ident', new nodes.Literal(c));
    }
  },

  /**
   * '@css' ' '* '{' .* '}' ' '*
   */

  get literal() {
    // HACK attack !!!
    var captures;
    if (captures = /^@css *\{/.exec(this.str)) {
      this.skip(captures);
      var c
        , braces = 1
        , css = '';
      while (c = this.str[0]) {
        this.str = this.str.substr(1);
        switch (c) {
          case '{': ++braces; break;
          case '}': --braces; break;
        }
        css += c;
        if (!braces) break;
      }
      css = css.replace(/\s*}$/, '');
      return new Token('literal', new nodes.Literal(css));
    }
  },

  /**
   * '!important' ' '*
   */

  get important() {
    var captures;
    if (captures = /^!important */.exec(this.str)) {
      this.skip(captures);
      return new Token('ident', new nodes.Literal('!important'));
    }
  },

  /**
   * '{' | '}'
   */

  get brace() {
    var captures;
    if (captures = /^([{}])/.exec(this.str)) {
      this.skip(1);
      var brace = captures[1];
      return new Token(brace, brace);
    }
  },

  /**
   * '(' | ')' ' '*
   */

  get paren() {
    var captures;
    if (captures = /^([()]) */.exec(this.str)) {
      var paren = captures[1];
      this.skip(captures);
      if (')' == paren) this.isURL = false;
      return new Token(paren, paren);
    }
  },

  /**
   * 'null'
   */

  get null() {
    var captures;
    if (captures = /^(null)\b */.exec(this.str)) {
      this.skip(captures);
      return new Token('null', nodes.null);
    }
  },

  /**
   *   'if'
   * | 'else'
   * | 'unless'
   * | 'return'
   * | 'for'
   * | 'in'
   */

  get keyword() {
    var captures;
    if (captures = /^(return|if|else|unless|for|in)\b */.exec(this.str)) {
      var keyword = captures[1];
      this.skip(captures);
      return new Token(keyword, keyword);
    }
  },

  /**
   *   'not'
   * | 'and'
   * | 'or'
   * | 'is a'
   * | 'is defined'
   */

  get namedop() {
    var captures;
    if (captures = /^(not|and|or|is a|is defined)\b( *)/.exec(this.str)) {
      var op = captures[1];
      this.skip(captures);
      op = alias[op] || op;
      var tok = new Token(op, op);
      tok.space = captures[2];
      return tok;
    }
  },

  /**
   *   ','
   * | '+'
   * | '+='
   * | '-'
   * | '-='
   * | '*'
   * | '*='
   * | '/'
   * | '/='
   * | '%'
   * | '%='
   * | '**'
   * | '!'
   * | '&'
   * | '&&'
   * | '||'
   * | '>'
   * | '>='
   * | '<'
   * | '<='
   * | '='
   * | '=='
   * | '!='
   * | '!'
   * | '~'
   * | '?='
   * | '?'
   * | ':'
   * | '['
   * | ']'
   * | '..'
   * | '...'
   */

  get op() {
    var captures;
    if (captures = /^([.]{2,3}|&&|\|\||[!<>=?]=|\*\*|[-+*\/%]=?|[,=?:!~<>&\[\]])( *)/.exec(this.str)) {
      var op = captures[1];
      this.skip(captures);
      op = alias[op] || op;
      var tok = new Token(op, op);
      tok.space = captures[2];
      return tok;
    }
  },

  /**
   * '@media' ([^\n]+)
   */

  get media() {
    var captures;
    if (captures = /^@media *([^\n]+)/.exec(this.str)) {
      this.skip(captures);
      return new Token('media', captures[1]);
    }
  },

  /**
   * '@' ('import' | 'keyframes' | 'charset' | 'page')
   */

  get atrule() {
    var captures;
    if (captures = /^@(import|keyframes|charset|page) */.exec(this.str)) {
      this.skip(captures);
      return new Token(captures[1]);
    }
  },

  /**
   * '//' *
   */

  get comment() {
    // Single line
    if ('/' == this.str[0] && '/' == this.str[1]) {
      var end = this.str.indexOf('\n');
      if (-1 == end) end = this.str.length;
      this.skip(end);
      return this.advance;
    }

    // Multi-line
    if ('/' == this.str[0] && '*' == this.str[1]) {
      var end = this.str.indexOf('*/');
      if (-1 == end) end = this.str.length;
      var str = this.str.substr(0, end + 2)
        , lines = str.split('\n').length - 1;
      this.lineno += lines;
      this.skip(end + 2);
      return this.allowComments
        ? new Token('comment', str)
        : this.advance;
    }
  },

  /**
   * 'true' | 'false'
   */

  get boolean() {
    var captures;
    if (captures = /^(true|false)\b( *)/.exec(this.str)) {
      var val = 'true' == captures[1]
        ? nodes.true
        : nodes.false;
      this.skip(captures);
      var tok = new Token('boolean', val);
      tok.space = captures[2];
      return tok;
    }
  },

  /**
   * -?[a-zA-Z] [-\w\d]* '('
   */

  get function() {
    var captures;
    if (captures = /^(-?[a-zA-Z][-\w\d]*)\(( *)/.exec(this.str)) {
      var name = captures[1];
      this.skip(captures);
      this.isURL = 'url' == name;
      var tok = new Token('function', new nodes.Ident(name));
      tok.space = captures[2];
      return tok;
    }
  },

  /**
   * -?[a-zA-Z] [-\w\d]*
   */

  get ident() {
    var captures;
    if (captures = /^(-?[a-zA-Z][-\w\d]*)/.exec(this.str)) {
      var name = captures[1];
      this.skip(captures);
      return new Token('ident', new nodes.Ident(name));
    }
  },

  /**
   * '\n' ' '+
   */

  get newline() {
    var captures;
    if (captures = /^\n( *)/.exec(this.str)) {
      var tok
        , spaces = captures[1].length
        , indents = spaces / 2;

      this.skip(captures);

      // Reset state
      this.isVariable = false;

      // Blank line
      if ('\n' == this.str[0]) {
        ++this.lineno;
        return this.advance;
      }

      // To few spaces
      if (0 != spaces % 2) {
        var str = 1 == spaces ? 'space' : 'spaces'
          , spaces = numberString[spaces] || spaces;
        throw new Error('Invalid indentation, got ' + spaces + ' ' + str + ' and expected multiple of two');
      // To many spaces
      } else if (indents > this.prevIndents + 1) {
        var str = 1 == spaces ? 'space' : 'spaces'
          , expected = 2 * (this.prevIndents * 2) || 2
          , expected = numberString[expected] || expected
          , spaces = numberString[spaces] || spaces;
        throw new Error('Invalid indentation, got ' + spaces + ' ' + str + ' and expected ' + expected);
      // Outdent
      } else if (indents < this.prevIndents) {
        var n = this.prevIndents - indents;
        while (--n) this.stash.push(new Token('outdent'));
        this.prevIndents = indents;
        tok = new Token('outdent');
      // Indent
      } else if (indents != this.prevIndents) {
        this.prevIndents = indents;
        tok = new Token('indent');
      // Newline
      } else {
        tok = new Token('newline');
      }

      return tok;
    }
  },

  /**
   * '-'? (digit+ | digit* '.' digit+) unit
   */

  get unit() {
    var captures;
    if (captures = unit.exec(this.str)) {
      this.skip(captures);
      var n = parseFloat(captures[2]);
      if ('-' == captures[1]) n = -n;
      var node = new nodes.Unit(n, captures[3]);
      return new Token('unit', node);
    }
  },

  /**
   * '"' [^"]+ '"' | "'"" [^']+ "'"
   */

  get string() {
    var captures;

    // url() special case
    if (this.isURL && (captures = /^([^'"][^)]+) */.exec(this.str))) {
      this.skip(captures);
      return new Token('string', new nodes.String(captures[1]));
    }

    // Regular string
    if (captures = /^("[^"]*"|'[^']*') */.exec(this.str)) {
      var str = captures[1];
      this.skip(captures);
      return new Token('string', new nodes.String(str.slice(1,-1)));
    }
  },

  /**
   * #nnnnnn | #nnn
   */

  get color() {
    return this.hex6 || this.hex3;
  },

  /**
   * #nnn
   */

  get hex3() {
    var captures;
    if (captures = /^#([a-f-A-F0-9]{3}) */.exec(this.str)) {
      this.skip(captures);
      var rgb = captures[1]
        , r = parseInt(rgb[0] + rgb[0], 16)
        , g = parseInt(rgb[1] + rgb[1], 16)
        , b = parseInt(rgb[2] + rgb[2], 16);
      return new Token('color', new nodes.Color(r, g, b, 1));
    }
  },

  /**
   * #nnnnnn
   */

  get hex6() {
    var captures;
    if (captures = /^#([a-f-A-F0-9]{6}) */.exec(this.str)) {
      this.skip(captures);
      var rgb = captures[1]
        , r = parseInt(rgb.substr(0, 2), 16)
        , g = parseInt(rgb.substr(2, 2), 16)
        , b = parseInt(rgb.substr(4, 2), 16);
      return new Token('color', new nodes.Color(r, g, b, 1));
    }
  },

  /**
   * [^\n,;]+
   */

  get selector() {
    var captures;
    if (captures = /^[^\n,]+/.exec(this.str)) {
      var selector = captures[0];
      this.skip(captures);
      return new Token('selector', selector);
    }
  }
};
<commit_msg>Scanning ;<commit_after>
/*!
 * CSS - Lexer
 * Copyright(c) 2010 LearnBoost <dev@learnboost.com>
 * MIT Licensed
 */

/**
 * Module dependencies.
 */

var Token = require('./token')
  , nodes = require('./nodes');

/**
 * Operator aliases.
 */

var alias = {
    and: '&&'
  , or: '||'
};

/**
 * Numeric strings used for exceptions.
 */

var numberString = [, 'one', 'two'];

/**
 * Units.
 */

var units = [
    'em'
  , 'ex'
  , 'px'
  , 'mm'
  , 'cm'
  , 'in'
  , 'pt'
  , 'pc'
  , 'deg'
  , 'rad'
  , 'grad'
  , 'ms'
  , 's'
  , 'Hz'
  , 'kHz'
  , '%'].join('|');

/**
 * Unit RegExp.
 */

var unit = new RegExp('^(-)?(\\d+\\.\\d+|\\d+|\\.\\d+)(' + units + ')? *');

/**
 * Initialize a new `Lexer` with the given `str` and `options`.
 *
 * @param {String} str
 * @param {Object} options
 * @api private
 */

var Lexer = module.exports = function Lexer(str, options) {
  options = options || {};
  this.str = str.replace(/\r\n?/g, '\n').replace(/\t/g, '  ');
  this.stash = [];
  this.prevIndents = 0;
  this.lineno = 0;
};

/**
 * Lexer prototype.
 */

Lexer.prototype = {

  /**
   * Custom inspect.
   */

  inspect: function(){
    var tok
      , tmp = this.str
      , buf = [];
    while ('eos' != (tok = this.next).type) {
      buf.push(tok.inspect());
    }
    this.str = tmp;
    this.prevIndents = 0;
    return buf.concat(tok.inspect()).join('\n');
  },

  /**
   * Lookahead `n` tokens.
   *
   * @param {Number} n
   * @return {Object}
   * @api private
   */

  lookahead: function(n){
    var fetch = n - this.stash.length;
    while (fetch-- > 0) this.stash.push(this.advance);
    return this.stash[--n];
  },

  /**
   * Consume the given `len`.
   *
   * @param {Number|Array} len
   * @api private
   */

  skip: function(len){
    this.str = this.str.substr(Array.isArray(len)
      ? len[0].length
      : len);
  },

  /**
   * Fetch next token including those stashed by peek.
   *
   * @return {Token}
   * @api private
   */

  get next() {
    var tok = this.stashed || this.advance;
    switch (tok.type) {
      case 'newline':
      case 'selector':
      case 'indent':
        ++this.lineno;
    }
    tok.lineno = this.lineno;
    return tok;
  },

  /**
   * Fetch next token.
   *
   * @return {Token}
   * @api private
   */

  get advance() {
    return this.eos
      || this.null
      || this.sep
      || this.keyword
      || this.atrule
      || this.media
      || this.comment
      || this.newline
      || this.escaped
      || this.important
      || this.literal
      || this.function
      || this.brace
      || this.paren
      || this.color
      || this.string
      || this.unit
      || this.namedop
      || this.boolean
      || this.ident
      || this.op
      || this.space
      || this.selector;
  },

  /**
   * Lookahead a single token.
   *
   * @return {Token}
   * @api private
   */

  get peek() {
    return this.lookahead(1);
  },

  /**
   * Return the next possibly stashed token.
   *
   * @return {Token}
   * @api private
   */

  get stashed() {
    return this.stash.shift();
  },

  /**
   * EOS | trailing outdents.
   */

  get eos() {
    if (this.str.length) return;
    return --this.prevIndents < 0
      ? new Token('eos')
      : new Token('outdent');
  },

  /**
   * ';' ' '*
   */

  get sep() {
    var captures;
    if (captures = /^; */.exec(this.str)) {
      this.skip(captures);
      return new Token(';');
    }
  },

  /**
   * ' '+
   */

  get space() {
    var captures;
    if (captures = /^( +)/.exec(this.str)) {
      this.skip(captures);
      return new Token('space');
    }
  },

  /**
   * '\\' . ' '*
   */

  get escaped() {
    var captures;
    if (captures = /^\\(.) */.exec(this.str)) {
      var c = captures[1];
      this.skip(captures);
      return new Token('ident', new nodes.Literal(c));
    }
  },

  /**
   * '@css' ' '* '{' .* '}' ' '*
   */

  get literal() {
    // HACK attack !!!
    var captures;
    if (captures = /^@css *\{/.exec(this.str)) {
      this.skip(captures);
      var c
        , braces = 1
        , css = '';
      while (c = this.str[0]) {
        this.str = this.str.substr(1);
        switch (c) {
          case '{': ++braces; break;
          case '}': --braces; break;
        }
        css += c;
        if (!braces) break;
      }
      css = css.replace(/\s*}$/, '');
      return new Token('literal', new nodes.Literal(css));
    }
  },

  /**
   * '!important' ' '*
   */

  get important() {
    var captures;
    if (captures = /^!important */.exec(this.str)) {
      this.skip(captures);
      return new Token('ident', new nodes.Literal('!important'));
    }
  },

  /**
   * '{' | '}'
   */

  get brace() {
    var captures;
    if (captures = /^([{}])/.exec(this.str)) {
      this.skip(1);
      var brace = captures[1];
      return new Token(brace, brace);
    }
  },

  /**
   * '(' | ')' ' '*
   */

  get paren() {
    var captures;
    if (captures = /^([()]) */.exec(this.str)) {
      var paren = captures[1];
      this.skip(captures);
      if (')' == paren) this.isURL = false;
      return new Token(paren, paren);
    }
  },

  /**
   * 'null'
   */

  get null() {
    var captures;
    if (captures = /^(null)\b */.exec(this.str)) {
      this.skip(captures);
      return new Token('null', nodes.null);
    }
  },

  /**
   *   'if'
   * | 'else'
   * | 'unless'
   * | 'return'
   * | 'for'
   * | 'in'
   */

  get keyword() {
    var captures;
    if (captures = /^(return|if|else|unless|for|in)\b */.exec(this.str)) {
      var keyword = captures[1];
      this.skip(captures);
      return new Token(keyword, keyword);
    }
  },

  /**
   *   'not'
   * | 'and'
   * | 'or'
   * | 'is a'
   * | 'is defined'
   */

  get namedop() {
    var captures;
    if (captures = /^(not|and|or|is a|is defined)\b( *)/.exec(this.str)) {
      var op = captures[1];
      this.skip(captures);
      op = alias[op] || op;
      var tok = new Token(op, op);
      tok.space = captures[2];
      return tok;
    }
  },

  /**
   *   ','
   * | '+'
   * | '+='
   * | '-'
   * | '-='
   * | '*'
   * | '*='
   * | '/'
   * | '/='
   * | '%'
   * | '%='
   * | '**'
   * | '!'
   * | '&'
   * | '&&'
   * | '||'
   * | '>'
   * | '>='
   * | '<'
   * | '<='
   * | '='
   * | '=='
   * | '!='
   * | '!'
   * | '~'
   * | '?='
   * | '?'
   * | ':'
   * | '['
   * | ']'
   * | '..'
   * | '...'
   */

  get op() {
    var captures;
    if (captures = /^([.]{2,3}|&&|\|\||[!<>=?]=|\*\*|[-+*\/%]=?|[,=?:!~<>&\[\]])( *)/.exec(this.str)) {
      var op = captures[1];
      this.skip(captures);
      op = alias[op] || op;
      var tok = new Token(op, op);
      tok.space = captures[2];
      return tok;
    }
  },

  /**
   * '@media' ([^\n]+)
   */

  get media() {
    var captures;
    if (captures = /^@media *([^\n]+)/.exec(this.str)) {
      this.skip(captures);
      return new Token('media', captures[1]);
    }
  },

  /**
   * '@' ('import' | 'keyframes' | 'charset' | 'page')
   */

  get atrule() {
    var captures;
    if (captures = /^@(import|keyframes|charset|page) */.exec(this.str)) {
      this.skip(captures);
      return new Token(captures[1]);
    }
  },

  /**
   * '//' *
   */

  get comment() {
    // Single line
    if ('/' == this.str[0] && '/' == this.str[1]) {
      var end = this.str.indexOf('\n');
      if (-1 == end) end = this.str.length;
      this.skip(end);
      return this.advance;
    }

    // Multi-line
    if ('/' == this.str[0] && '*' == this.str[1]) {
      var end = this.str.indexOf('*/');
      if (-1 == end) end = this.str.length;
      var str = this.str.substr(0, end + 2)
        , lines = str.split('\n').length - 1;
      this.lineno += lines;
      this.skip(end + 2);
      return this.allowComments
        ? new Token('comment', str)
        : this.advance;
    }
  },

  /**
   * 'true' | 'false'
   */

  get boolean() {
    var captures;
    if (captures = /^(true|false)\b( *)/.exec(this.str)) {
      var val = 'true' == captures[1]
        ? nodes.true
        : nodes.false;
      this.skip(captures);
      var tok = new Token('boolean', val);
      tok.space = captures[2];
      return tok;
    }
  },

  /**
   * -?[a-zA-Z] [-\w\d]* '('
   */

  get function() {
    var captures;
    if (captures = /^(-?[a-zA-Z][-\w\d]*)\(( *)/.exec(this.str)) {
      var name = captures[1];
      this.skip(captures);
      this.isURL = 'url' == name;
      var tok = new Token('function', new nodes.Ident(name));
      tok.space = captures[2];
      return tok;
    }
  },

  /**
   * -?[a-zA-Z] [-\w\d]*
   */

  get ident() {
    var captures;
    if (captures = /^(-?[a-zA-Z][-\w\d]*)/.exec(this.str)) {
      var name = captures[1];
      this.skip(captures);
      return new Token('ident', new nodes.Ident(name));
    }
  },

  /**
   * '\n' ' '+
   */

  get newline() {
    var captures;
    if (captures = /^\n( *)/.exec(this.str)) {
      var tok
        , spaces = captures[1].length
        , indents = spaces / 2;

      this.skip(captures);

      // Reset state
      this.isVariable = false;

      // Blank line
      if ('\n' == this.str[0]) {
        ++this.lineno;
        return this.advance;
      }

      // To few spaces
      if (0 != spaces % 2) {
        var str = 1 == spaces ? 'space' : 'spaces'
          , spaces = numberString[spaces] || spaces;
        throw new Error('Invalid indentation, got ' + spaces + ' ' + str + ' and expected multiple of two');
      // To many spaces
      } else if (indents > this.prevIndents + 1) {
        var str = 1 == spaces ? 'space' : 'spaces'
          , expected = 2 * (this.prevIndents * 2) || 2
          , expected = numberString[expected] || expected
          , spaces = numberString[spaces] || spaces;
        throw new Error('Invalid indentation, got ' + spaces + ' ' + str + ' and expected ' + expected);
      // Outdent
      } else if (indents < this.prevIndents) {
        var n = this.prevIndents - indents;
        while (--n) this.stash.push(new Token('outdent'));
        this.prevIndents = indents;
        tok = new Token('outdent');
      // Indent
      } else if (indents != this.prevIndents) {
        this.prevIndents = indents;
        tok = new Token('indent');
      // Newline
      } else {
        tok = new Token('newline');
      }

      return tok;
    }
  },

  /**
   * '-'? (digit+ | digit* '.' digit+) unit
   */

  get unit() {
    var captures;
    if (captures = unit.exec(this.str)) {
      this.skip(captures);
      var n = parseFloat(captures[2]);
      if ('-' == captures[1]) n = -n;
      var node = new nodes.Unit(n, captures[3]);
      return new Token('unit', node);
    }
  },

  /**
   * '"' [^"]+ '"' | "'"" [^']+ "'"
   */

  get string() {
    var captures;

    // url() special case
    if (this.isURL && (captures = /^([^'"][^)]+) */.exec(this.str))) {
      this.skip(captures);
      return new Token('string', new nodes.String(captures[1]));
    }

    // Regular string
    if (captures = /^("[^"]*"|'[^']*') */.exec(this.str)) {
      var str = captures[1];
      this.skip(captures);
      return new Token('string', new nodes.String(str.slice(1,-1)));
    }
  },

  /**
   * #nnnnnn | #nnn
   */

  get color() {
    return this.hex6 || this.hex3;
  },

  /**
   * #nnn
   */

  get hex3() {
    var captures;
    if (captures = /^#([a-f-A-F0-9]{3}) */.exec(this.str)) {
      this.skip(captures);
      var rgb = captures[1]
        , r = parseInt(rgb[0] + rgb[0], 16)
        , g = parseInt(rgb[1] + rgb[1], 16)
        , b = parseInt(rgb[2] + rgb[2], 16);
      return new Token('color', new nodes.Color(r, g, b, 1));
    }
  },

  /**
   * #nnnnnn
   */

  get hex6() {
    var captures;
    if (captures = /^#([a-f-A-F0-9]{6}) */.exec(this.str)) {
      this.skip(captures);
      var rgb = captures[1]
        , r = parseInt(rgb.substr(0, 2), 16)
        , g = parseInt(rgb.substr(2, 2), 16)
        , b = parseInt(rgb.substr(4, 2), 16);
      return new Token('color', new nodes.Color(r, g, b, 1));
    }
  },

  /**
   * [^\n,;]+
   */

  get selector() {
    var captures;
    if (captures = /^[^\n,]+/.exec(this.str)) {
      var selector = captures[0];
      this.skip(captures);
      return new Token('selector', selector);
    }
  }
};
<|endoftext|>


<commit_before>var Collate = require('./collate');

// A sorted store of javascript key, value pairs where 'key' can be anything
// that's defined in the collator above. Ideally, this needs to be a BTree,
// but this is a stop-gap solution. The interface to the Store remains
// useful, if and when we implement a BTree.
//
// Each [k,v] pair also has an associated id (defaults to undefined). This
// allows us to have multiple identical [k,v] pairs in the store that are
// still considered unique if the ids of them are different. The [k,v] pairs
// are first collated by the 'k' and then further collated by the id.
var Store = function() {
    var _size = 0;
    var _nodes = [];

    // Binary search the array for 'key' and return the index at which the
    // key was found or should be inserted. If the return object also has 'node',
    // then the 'key' was successully found at the 'index'. This essentially
    // gives us O(log(n)) insert performance, though the cost of each compare2
    // grows as the complexity of array/object that makes up the key increases.
    // If the insertions are pre-sorted (meaning the keys are already collated)
    // then all this is is an array.push. However, for insertions of random
    // 1,000,000 entries, this starts to slow down because of the 'memmoves'
    // in v8's array implementation.
    var _find = function(key, id, key_only_match) {
        var min = 0, max = _nodes.length - 1, n, key2, c, id2, node;
        var tkey = Collate.type(key), tkey2;
        var tid = Collate.type(id), tid2;

        if (_nodes.length === 0) {
            return { index: 0, tkey: tkey, tid: tid };
        }

        do {
            n = Math.floor(min + (max - min)/2);
            node  = _nodes[n];
            id2   = node.id;
            key2  = node.key;
            tkey2 = node.tkey;
            tid2 = node.tid;
            c = Collate.compare2(key, tkey, key2, tkey2);
            if (c === 0) {
                if (!key_only_match) {
                    c = Collate.compare2(id, tid, id2, tid2);
                }
            }

            if (c > 0) {
                min = n + 1;
            } else if (c < 0) {
                max = n - 1;
            } else {
                return { index: n, node: _nodes[n], tid: tid, tkey: tkey };
            }
        } while (min <= max);

        return { index: min, tid: tid, tkey: tkey };
    };

    return {
        size: function() {
            return _size;
        },
        insert: function(key, val, id) {
            var r = _find(key, id);
            if (r.node) {
                r.node.val = val;
            } else {
                _nodes.splice(r.index, 0, { id: id, key: key, val: val, tkey: r.tkey, tid: r.tid });
                _size += 1;
            }
        },
        insert2: function(key, id, cb) {
            if (cb === undefined) {
                cb = id;
                id = undefined;
            }
            var r = _find(key, id);
            if (r.node) {
                return cb(r.node.val, function(val) {
                    r.node.val = val;
                });
            } else {
                return cb(undefined, function(val) {
                    _nodes.splice(r.index, 0, { id: id, key: key, val: val, tkey: r.tkey, tid: r.tid });
                    _size += 1;
                });
            }
        },
        find: function(key, id) {
            var r = _find(key, id);
            return r.node ? r.node.val : undefined;
        },
        findFirst: function(key) {
            var r = _find(key, undefined, true);
            return r.node ? r.node.val : undefined;
        },
        erase: function(key, id) {
            var r = _find(key, id);
            if (r.node) {
                _nodes.splice(r.index, 1);
                _size -= 1;
                return r.node.val;
            }
            return undefined;
        },
        erase2: function(key, id, cb) {
            if (cb === undefined) {
                cb = id;
                id = undefined;
            }
            var r = _find(key, id);
            if (r.node) {
                return cb(r.node.val, function() {
                    _nodes.splice(r.index, 1);
                    _size -= 1;
                });
            } else {
                return cb(undefined);
            }
        },
        each: function(opts, cb) {
            if (cb === undefined) {
                cb = opts;
                opts = {};
            }

            var start, end, first, last, count, n, node;
            if (_nodes.length === 0) {
                return;
            }

            if (opts.startkey) {
                // TODO: If the startkey_docid is not present, then we should
                // match on just the key and ignore any 'id's.
                start = _find(opts.startkey);
                if (! start.node) {
                    return;
                }
            } else {
                if (opts.descending) {
                    first = _nodes[_nodes.length - 1];
                    start = { index: _nodes.length - 1, node: first, type: first.type };
                } else {
                    first = _nodes[0];
                    start = { index: 0, node: first, type: first.type };
                }
            }

            if (opts.endkey) {
                end = _find(opts.endkey);
                if (! end.node) {
                    return;
                }
            } else {
                if (opts.descending) {
                    first = _nodes[0];
                    end = { index: 0, node: first, type: first.type };
                } else {
                    last = _nodes[_nodes.length-1];
                    end = { index: _nodes.length - 1, node: last, type: last.type };
                }
            }

            // TODO: Does it make sense to do async here so we don't lock
            // up other clients. Since we already have a callback given to us
            // we can take our time walking the list. Downside is the other
            // clients might be making changes to the list which means the
            // iterator needs to account for the _nodes array getting modified
            // while we are iterating.
            count = opts.limit === undefined ? Math.abs(start.index - end.index) + 1 : opts.limit;
            if (opts.descending) {
                for (n = start.index; count > 0 && n >= end.index; --n, --count) {
                    node = _nodes[n];
                    if (cb(node.key, node.val, node.id, node) === false) {
                        break;
                    }
                }
            } else {
                for (n = start.index; count > 0 && n <= end.index; ++n, --count) {
                    node = _nodes[n];
                    if (cb(node.key, node.val, node.id, node) === false) {
                        break;
                    }
                }
            }
        },
        _index: function(key, id) {
            var r = _find(key, id);
            return r.node ? r.index : -1;
        },
        _dump: function() {
            console.log(_nodes);
        }
    }
};

this.create = function() {
    return new Store();
};
<commit_msg>tweaks after runing futon tests<commit_after>var Collate = require('./collate');

// A sorted store of javascript key, value pairs where 'key' can be anything
// that's defined in the collator above. Ideally, this needs to be a BTree,
// but this is a stop-gap solution. The interface to the Store remains
// useful, if and when we implement a BTree.
//
// Each [k,v] pair also has an associated id (defaults to undefined). This
// allows us to have multiple identical [k,v] pairs in the store that are
// still considered unique if the ids of them are different. The [k,v] pairs
// are first collated by the 'k' and then further collated by the id.
var Store = function() {
    var _size = 0;
    var _nodes = [];

    // Binary search the array for 'key' and return the index at which the
    // key was found or should be inserted. If the return object also has 'node',
    // then the 'key' was successully found at the 'index'. This essentially
    // gives us O(log(n)) insert performance, though the cost of each compare2
    // grows as the complexity of array/object that makes up the key increases.
    // If the insertions are pre-sorted (meaning the keys are already collated)
    // then all this is is an array.push. However, for insertions of random
    // 1,000,000 entries, this starts to slow down because of the 'memmoves'
    // in v8's array implementation.
    var _find = function(key, id, key_only_match) {
        var min = 0, max = _nodes.length - 1, n, key2, c, id2, node;
        var tkey = Collate.type(key), tkey2;
        var tid = Collate.type(id), tid2;

        if (_nodes.length === 0) {
            return { index: 0, tkey: tkey, tid: tid };
        }

        do {
            n = Math.floor(min + (max - min)/2);
            node  = _nodes[n];
            id2   = node.id;
            key2  = node.key;
            tkey2 = node.tkey;
            tid2 = node.tid;
            c = Collate.compare2(key, tkey, key2, tkey2);
            if (c === 0) {
                if (!key_only_match) {
                    c = Collate.compare2(id, tid, id2, tid2);
                }
            }

            if (c > 0) {
                min = n + 1;
            } else if (c < 0) {
                max = n - 1;
            } else {
                return { index: n, node: _nodes[n], tid: tid, tkey: tkey };
            }
        } while (min <= max);

        return { index: min, tid: tid, tkey: tkey };
    };

    return {
        size: function() {
            return _size;
        },
        insert: function(key, val, id) {
            var r = _find(key, id);
            if (r.node) {
                r.node.val = val;
            } else {
                _nodes.splice(r.index, 0, { id: id, key: key, val: val, tkey: r.tkey, tid: r.tid });
                _size += 1;
            }
        },
        insert2: function(key, id, cb) {
            if (cb === undefined) {
                cb = id;
                id = undefined;
            }
            var r = _find(key, id);
            if (r.node) {
                return cb(r.node.val, function(val) {
                    r.node.val = val;
                });
            } else {
                return cb(undefined, function(val) {
                    _nodes.splice(r.index, 0, { id: id, key: key, val: val, tkey: r.tkey, tid: r.tid });
                    _size += 1;
                });
            }
        },
        find: function(key, id) {
            var r = _find(key, id);
            return r.node ? r.node.val : undefined;
        },
        findFirst: function(key) {
            var r = _find(key, undefined, true);
            return r.node ? r.node.val : undefined;
        },
        erase: function(key, id) {
            var r = _find(key, id);
            if (r.node) {
                _nodes.splice(r.index, 1);
                _size -= 1;
                return r.node.val;
            }
            return undefined;
        },
        erase2: function(key, id, cb) {
            if (cb === undefined) {
                cb = id;
                id = undefined;
            }
            var r = _find(key, id);
            if (r.node) {
                return cb(r.node.val, function() {
                    _nodes.splice(r.index, 1);
                    _size -= 1;
                });
            } else {
                return cb(undefined);
            }
        },
        each: function(opts, cb) {
            if (cb === undefined) {
                cb = opts;
                opts = {};
            }

            var start, end, first, last, count, n, node;
            if (_nodes.length === 0) {
                return { total_rows: 0 };
            }

            if (opts.key !== undefined) {
                start = _find(opts.key, undefined, true);
                if (! start.node) {
                    return { total_rows: _nodes.length };
                }
                node = _nodes[start.index];
                cb(node.key, node.val, node.id, node);
                return { total_rows: _nodes.length, offset: start.index };
            } else {
                if (opts.startkey != undefined) {
                    start = _find(opts.startkey, opts.startkey_docid, true);
                    if (! start.node) {
                        return { total_rows: _nodes.length };
                    }
                } else {
                    if (opts.descending !== undefined) {
                        first = _nodes[_nodes.length - 1];
                        start = { index: _nodes.length - 1, node: first, type: first.type };
                    } else {
                        first = _nodes[0];
                        start = { index: 0, node: first, type: first.type };
                    }
                }

                if (opts.endkey !== undefined) {
                    end = _find(opts.endkey, opts.endkey_docid, true);
                    if (! end.node) {
                        return { total_rows: _nodes.length };
                    }
                } else {
                    if (opts.descending !== undefined) {
                        first = _nodes[0];
                        end = { index: 0, node: first, type: first.type };
                    } else {
                        last = _nodes[_nodes.length-1];
                        end = { index: _nodes.length - 1, node: last, type: last.type };
                    }
                }
            }

            count = opts.limit === undefined ? Math.abs(start.index - end.index) + 1 : opts.limit;
            if (opts.descending !== undefined) {
                for (n = start.index; count > 0 && n >= end.index; --n, --count) {
                    node = _nodes[n];
                    if (cb(node.key, node.val, node.id, node) === false) {
                        break;
                    }
                }
            } else {
                for (n = start.index; count > 0 && n <= end.index; ++n, --count) {
                    node = _nodes[n];
                    if (cb(node.key, node.val, node.id, node) === false) {
                        break;
                    }
                }
            }

            return { total_rows: _nodes.length, offset: start.index };
        },
        _index: function(key, id) {
            var r = _find(key, id);
            return r.node ? r.index : -1;
        },
        _dump: function() {
            console.log(_nodes);
        }
    }
};

this.create = function() {
    return new Store();
};
<|endoftext|>


<commit_before>function show_annotations(attachment_id, div_id) {
    // uses jqModal to show annotations in an ajaxy way.  Pass in the attachment
    // to annotate, and the div where you want to display your ajax.
    $(div_id).jqm({
          ajax: '/receiver/annotations/' + attachment_id,
          trigger: 'div.annotationtrigger',
          ajaxText: 'Please wait while we load that for you'
        });
}<commit_msg>show annotations pop up on first click<commit_after>function show_annotations(attachment_id, div_id) {
    // uses jqModal to show annotations in an ajaxy way.  Pass in the attachment
    // to annotate, and the div where you want to display your ajax.
    $(div_id).jqm({
        ajax: '/receiver/annotations/' + attachment_id,
        trigger: 'div.annotationtrigger',
        ajaxText: 'Please wait while we load that for you'
    });

    $(div_id).jqmShow();
}<|endoftext|>