<commit_before>Ext.define('CustomApp', {
    extend: 'Rally.app.App',
    componentCls: 'app',
    width: 800,
    layout:{
      type: 'vbox',
    },
    items:[
      {
        xtype: 'container',
        itemId: 'ribbon',
        html: 'ribbon!',
        width: 1000,
        height: 250,
        border: 1,
        style: {
          borderColor: '#AAA',
          borderStyle: 'solid'
        }
      },
      {
        xtype: 'container',
        itemId: 'gridsContainer',
        layout: {
          type: 'hbox',
          align: 'top'
        },
        items: [
          {
            xtype: 'container',
            itemId: 'gridsLeft',
            width: 500,
            border: 1,
            style: {
              borderColor: '#AAA',
              borderStyle: 'solid'
            }
          },
          {
            xtype: 'container',
            itemId: 'gridsRight',
            width: 500,
            border: 1,
            style: {
              borderColor: '#AAA',
              borderStyle: 'solid'
            }
          }
        ]
      }
    ],

    // App entry point
    launch: function() {
      this._buildGrids();
      this._buildCharts();
    },

    // Create all charts in the header ribbon
    _buildCharts: function() {
      this._buildPieChart();
      this._buildBubbleChart();
    },
    _buildPie2Chart: function() {

      var pieChart = Ext.create('Rally.ui.chart.Chart', {
        chartConfig: { // {{{
          chart: {
              plotBackgroundColor: null,
              plotBorderWidth: 1,//null,
              plotShadow: false,
              width: 300,
              height: 250,
          },
          title: {
              text: ''
          },
          tooltip: {
              pointFormat: '{series.name}: {point.percentage:.1f}%'
          },
          plotOptions: {
            pie: {
              allowPointSelect: true,
              cursor: 'pointer',
              dataLabels: {
                enabled: true,
                format: '{point.name}: {point.percentage:.1f} %',
                style: {
                    color: 'black'
                }
              }
            }
          }
        }, // }}}
        chartData: { // {{{
          series: [{
              type: 'pie',
              name: 'Browser share',
              data: [
                  ['Firefox',   45.0],
                  ['IE',       26.8],
                  {
                      name: 'Chrome',
                      y: 12.8,
                      sliced: true,
                      selected: true
                  },
                  ['Safari',    8.5],
                  ['Opera',     6.2],
                  ['Others',   0.7]
              ]
          }]
        } // }}}
      });

      var ribbonContainer = this.down('#ribbon');
      ribbonContainer.add(pieChart);

    },

    _buildPieChart: function() {

      var pieChart = Ext.create('Rally.ui.chart.Chart', {
        chartConfig: { // {{{
          chart: {
              plotBackgroundColor: null,
              plotBorderWidth: 1,//null,
              plotShadow: false,
              width: 300,
              height: 250,
          },
          title: {
              text: ''
          },
          tooltip: {
              pointFormat: '{series.name}: {point.percentage:.1f}%'
          },
          plotOptions: {
            pie: {
              allowPointSelect: true,
              cursor: 'pointer',
              dataLabels: {
                enabled: true,
                format: '{point.name}: {point.percentage:.1f} %',
                style: {
                    color: 'black'
                }
              }
            }
          }
        }, // }}}
        chartData: { // {{{
          series: [{
              type: 'pie',
              name: 'Browser share',
              data: [
                  ['Firefox',   45.0],
                  ['IE',       26.8],
                  {
                      name: 'Chrome',
                      y: 12.8,
                      sliced: true,
                      selected: true
                  },
                  ['Safari',    8.5],
                  ['Opera',     6.2],
                  ['Others',   0.7]
              ]
          }]
        } // }}}
      });

      var ribbonContainer = this.down('#ribbon');
      ribbonContainer.add(pieChart);

    },

    _buildBubbleChart: function() {

      var bubbleChart = Ext.create('Rally.ui.chart.Chart', {
        chartConfig: {
          chart: {
              width: 300,
              height: 250,
              type: 'bubble',
              zoomType: 'xy'
          },

          title: {
              text: 'Foo Bubbles'
          },
        },
        chartData: {
          series: [{
              data: [[97, 36, 79], [94, 74, 60], [68, 76, 58], [64, 87, 56], [68, 27, 73], [74, 99, 42], [7, 93, 87], [51, 69, 40], [38, 23, 33], [57, 86, 31]]
          }, {
              data: [[25, 10, 87], [2, 75, 59], [11, 54, 8], [86, 55, 93], [5, 3, 58], [90, 63, 44], [91, 33, 17], [97, 3, 56], [15, 67, 48], [54, 25, 81]]
          }, {
              data: [[47, 47, 21], [20, 12, 4], [6, 76, 91], [38, 30, 60], [57, 98, 64], [61, 17, 80], [83, 60, 13], [67, 78, 75], [64, 12, 10], [30, 77, 82]]
          }]
        }
      });

      var ribbonContainer = this.down('#ribbon');
      ribbonContainer.add(bubbleChart);

    },
    // Create all grids in the left/right columns
    _buildGrids: function() {

      var grids = [
        {
          title: 'Blocked Stories',
          model: 'User Story',
          columns: ['FormattedID', 'Name', 'Owner', 'Blocked'],
          side: 'Left',    // TODO: ensure camelcase format to match itemId names
          pageSize: 5,
          filters: function() {
            return Ext.create('Rally.data.wsapi.Filter', {
                property: 'blocked', operator: '=', value: 'true'
            });
          }
        },
        {
          title: 'Unsized Stories with Features',
          model: 'User Story',
          columns: ['FormattedID', 'Name', 'Owner', 'Feature'],
          side: 'Right',    // TODO: ensure camelcase format to match itemId names
          pageSize: 5,
          filters: function() {
            var featureFilter = Ext.create('Rally.data.wsapi.Filter', {
                property: 'Feature', operator: '!=', value: 'null'
            });
            var noPlanEstimateFilter = Ext.create('Rally.data.wsapi.Filter', {
                property: 'PlanEstimate', operator: '=', value: 'null'
            });

            return featureFilter.and(noPlanEstimateFilter);
          }
        },
        {
          title: 'Unsized Stories with Release',
          model: 'User Story',
          columns: ['FormattedID', 'Name', 'Owner', 'Release'],
          side: 'Left',    // TODO: ensure camelcase format to match itemId names
          pageSize: 5,
          filters: function() {
            var releaseFilter = Ext.create('Rally.data.wsapi.Filter', {
                property: 'Release', operator: '!=', value: 'null'
            });
            var noPlanEstimateFilter = Ext.create('Rally.data.wsapi.Filter', {
                property: 'PlanEstimate', operator: '=', value: 'null'
            });

            return releaseFilter.and(noPlanEstimateFilter);
          }
        },
        {
          title: 'Unsized Stories with Release',
          model: 'User Story',
          columns: ['FormattedID', 'Name', 'Owner', 'Release'],
          side: 'Right',    // TODO: ensure camelcase format to match itemId names
          pageSize: 5,
          filters: function() {
            var releaseFilter = Ext.create('Rally.data.wsapi.Filter', {
                property: 'Release', operator: '!=', value: 'null'
            });
            var noPlanEstimateFilter = Ext.create('Rally.data.wsapi.Filter', {
                property: 'PlanEstimate', operator: '=', value: 'null'
            });

            return releaseFilter.and(noPlanEstimateFilter);
          }
        },

      ];

      _.each(grids, function(grid) {
        this._addGrid(grid.title, grid.model, grid.columns, grid.filters, grid.side, grid.pageSize);
      }, this);
    },

    // Utility function to generically build a grid and add to a container with given specs
    _addGrid: function(myTitle, myModel, myColumns, myFilters, gridSide, pageSize) {
      // lookup left or right side
      var gridContainer = this.down('#grids' + gridSide);
      // new grid with store data
      var grid = Ext.create('Rally.ui.grid.Grid', {
        xtype: 'rallygrid',
        title: myTitle,
        columnCfgs: myColumns,
        storeConfig: {
          model: myModel,
          context: this.context.getDataContext(),
          autoLoad: {start: 0, limit: pageSize},
          filters: myFilters()
        }
      });
      // show me the grid!
      gridContainer.add(grid);

      /*
      var blockedStoriesStore = Ext.create('Rally.data.wsapi.Store', {
        model: 'UserStory',
        context: this.context.getDataContext(),
        autoLoad: {start: 0, limit: 5},
        filters: [
          {
            property: 'blocked',
            operator: '=',
            value: 'true'
          }
        ],
        listeners: {
          load: function(myStore, data) {
            console.log('got', data);
            var blockedStoriesGrid = Ext.create('Rally.ui.grid.Grid', {
              title: 'Blocked Stories',
              columnCfgs: ['FormattedID', 'Name', 'Owner'],
              store: myStore
            });
            var ls = this.down('#leftGrids');
            ls.add(blockedStoriesGrid);

          },
          scope: this
        }
      });
      */
    }

});
<commit_msg>changes to filters<commit_after>Ext.define('CustomApp', {
    extend: 'Rally.app.App',
    componentCls: 'app',
    width: 800,
    layout:{
      type: 'vbox',
    },
    items:[
      {
        xtype: 'container',
        itemId: 'ribbon',
        html: 'ribbon!',
        width: 1000,
        height: 250,
        border: 1,
        style: {
          borderColor: '#AAA',
          borderStyle: 'solid'
        }
      },
      {
        xtype: 'container',
        itemId: 'gridsContainer',
        padding: 25,
        layout: {
          type: 'hbox',
          align: 'top'
        },
        items: [
          {
            xtype: 'container',
            itemId: 'gridsLeft',
            width: 500,
            border: 1,
           // padding: 25,
            style: {
              borderColor: '#AAA',
              borderStyle: 'solid'
            }
          },
          {
            xtype: 'container',
            itemId: 'gridsRight',
            width: 500,
            border: 1,
           // padding: 25,
            style: {
              borderColor: '#AAA',
              borderStyle: 'solid'
            }
          }
        ]
      }
    ],

    // App entry point
    launch: function() {
      this._buildGrids();
      this._buildCharts();
    },

    // Create all charts in the header ribbon
    _buildCharts: function() {
      this._buildPieChart();
      this._buildBubbleChart();
    },
    _buildPie2Chart: function() {

      var pieChart = Ext.create('Rally.ui.chart.Chart', {
        chartConfig: { // {{{
          chart: {
              plotBackgroundColor: null,
              plotBorderWidth: 1,//null,
              plotShadow: false,
              width: 300,
              height: 250,
          },
          title: {
              text: ''
          },
          tooltip: {
              pointFormat: '{series.name}: {point.percentage:.1f}%'
          },
          plotOptions: {
            pie: {
              allowPointSelect: true,
              cursor: 'pointer',
              dataLabels: {
                enabled: true,
                format: '{point.name}: {point.percentage:.1f} %',
                style: {
                    color: 'black'
                }
              }
            }
          }
        }, // }}}
        chartData: { // {{{
          series: [{
              type: 'pie',
              name: 'Browser share',
              data: [
                  ['Firefox',   45.0],
                  ['IE',       26.8],
                  {
                      name: 'Chrome',
                      y: 12.8,
                      sliced: true,
                      selected: true
                  },
                  ['Safari',    8.5],
                  ['Opera',     6.2],
                  ['Others',   0.7]
              ]
          }]
        } // }}}
      });

      var ribbonContainer = this.down('#ribbon');
      ribbonContainer.add(pieChart);

    },

    _buildPieChart: function() {

      var pieChart = Ext.create('Rally.ui.chart.Chart', {
        chartConfig: { // {{{
          chart: {
              plotBackgroundColor: null,
              plotBorderWidth: 1,//null,
              plotShadow: false,
              width: 300,
              height: 250,
          },
          title: {
              text: ''
          },
          tooltip: {
              pointFormat: '{series.name}: {point.percentage:.1f}%'
          },
          plotOptions: {
            pie: {
              allowPointSelect: true,
              cursor: 'pointer',
              dataLabels: {
                enabled: true,
                format: '{point.name}: {point.percentage:.1f} %',
                style: {
                    color: 'black'
                }
              }
            }
          }
        }, // }}}
        chartData: { // {{{
          series: [{
              type: 'pie',
              name: 'Browser share',
              data: [
                  ['Firefox',   45.0],
                  ['IE',       26.8],
                  {
                      name: 'Chrome',
                      y: 12.8,
                      sliced: true,
                      selected: true
                  },
                  ['Safari',    8.5],
                  ['Opera',     6.2],
                  ['Others',   0.7]
              ]
          }]
        } // }}}
      });

      var ribbonContainer = this.down('#ribbon');
      ribbonContainer.add(pieChart);

    },

    _buildBubbleChart: function() {

      var bubbleChart = Ext.create('Rally.ui.chart.Chart', {
        chartConfig: {
          chart: {
              width: 300,
              height: 250,
              type: 'bubble',
              zoomType: 'xy'
          },

          title: {
              text: 'Foo Bubbles'
          },
        },
        chartData: {
          series: [{
              data: [[97, 36, 79], [94, 74, 60], [68, 76, 58], [64, 87, 56], [68, 27, 73], [74, 99, 42], [7, 93, 87], [51, 69, 40], [38, 23, 33], [57, 86, 31]]
          }, {
              data: [[25, 10, 87], [2, 75, 59], [11, 54, 8], [86, 55, 93], [5, 3, 58], [90, 63, 44], [91, 33, 17], [97, 3, 56], [15, 67, 48], [54, 25, 81]]
          }, {
              data: [[47, 47, 21], [20, 12, 4], [6, 76, 91], [38, 30, 60], [57, 98, 64], [61, 17, 80], [83, 60, 13], [67, 78, 75], [64, 12, 10], [30, 77, 82]]
          }]
        }
      });

      var ribbonContainer = this.down('#ribbon');
      ribbonContainer.add(bubbleChart);

    },
    // Create all grids in the left/right columns
    _buildGrids: function() {

      var grids = [
        {
          title: 'Blocked Stories',
          model: 'User Story',
          columns: ['FormattedID', 'Name', 'Owner', 'Blocked'],
          side: 'Left',    // TODO: ensure camelcase format to match itemId names
          pageSize: 5,
          filters: function() {
            return Ext.create('Rally.data.wsapi.Filter', {
                property: 'blocked', operator: '=', value: 'true'
            });
          }
        },
        {
          title: 'Unsized Stories with Features',
          model: 'User Story',
          columns: ['FormattedID', 'Name', 'Owner', 'Feature'],
          side: 'Right',    // TODO: ensure camelcase format to match itemId names
          pageSize: 5,
          filters: function() {
            var featureFilter = Ext.create('Rally.data.wsapi.Filter', {
                property: 'Feature', operator: '!=', value: 'null'
            });
            var noPlanEstimateFilter = Ext.create('Rally.data.wsapi.Filter', {
                property: 'PlanEstimate', operator: '=', value: 'null'
            });

            return featureFilter.and(noPlanEstimateFilter);
          }
        },
        {
          title: 'Unsized Stories with Release',
          model: 'User Story',
          columns: ['FormattedID', 'Name', 'Owner', 'Release'],
          side: 'Left',    // TODO: ensure camelcase format to match itemId names
          pageSize: 5,
          filters: function() {
            var releaseFilter = Ext.create('Rally.data.wsapi.Filter', {
                property: 'Release', operator: '!=', value: 'null'
            });
            var noPlanEstimateFilter = Ext.create('Rally.data.wsapi.Filter', {
                property: 'PlanEstimate', operator: '=', value: 'null'
            });

            return releaseFilter.and(noPlanEstimateFilter);
          }
        },
        {
          title: 'Features with no stories',
          model: 'PortfolioItem/Feature',
          columns: ['FormattedID', 'Name', 'PlannedEndDate'],
          side: 'Right',    // TODO: ensure camelcase format to match itemId names
          pageSize: 5,
          filters: function() {
            var userstoryFilter = Ext.create('Rally.data.wsapi.Filter', {
                property: 'UserStories.ObjectID', operator: '=', value: 'null'
            });
            var noPlanEstimateFilter = Ext.create('Rally.data.wsapi.Filter', {
                property: 'PlannedEndDate', operator: '<', value: 'NextWeek'
            });

            return userstoryFilter.and(noPlanEstimateFilter);
          }
        },
        {
          title: 'Features with no Iteration stories',
          model: 'UserStory',
          columns: ['FormattedID', 'Name', 'Feature'],
          side: 'Left',    // TODO: ensure camelcase format to match itemId names
          pageSize: 5,
          filters: function() {
            var featureFilter = Ext.create('Rally.data.wsapi.Filter', {
                property: 'Feature', operator: '!=', value: 'null'
            });
            var noPlanEstimateFilter = Ext.create('Rally.data.wsapi.Filter', {
                property: 'Iteration', operator: '=', value: 'null'
            });

            return featureFilter.and(noPlanEstimateFilter);
          }
        },
        {
          title: 'Features with unaccepted stories in past sprints',
          model: 'UserStory',
          columns: ['FormattedID', 'Name', 'Feature', 'ScheduleState'],
          side: 'Right',    // TODO: ensure camelcase format to match itemId names
          pageSize: 5,
          filters: function() {
            var featureFilter = Ext.create('Rally.data.wsapi.Filter', {
                property: 'Feature', operator: '!=', value: 'null'
            });
            var enddateFilter = Ext.create('Rally.data.wsapi.Filter', {
                property: 'Iteration.EndDate', operator: '<', value: 'Today'
            });
            var unacceptedFilter = Ext.create('Rally.data.wsapi.Filter', {
                property: 'ScheduleState', operator: '<', value: 'Accepted'
            });

            return featureFilter.and(unacceptedFilter).and(enddateFilter);
          }
        },

      ];

      _.each(grids, function(grid) {
        this._addGrid(grid.title, grid.model, grid.columns, grid.filters, grid.side, grid.pageSize);
      }, this);
    },

    // Utility function to generically build a grid and add to a container with given specs
    _addGrid: function(myTitle, myModel, myColumns, myFilters, gridSide, pageSize) {
      // lookup left or right side
      var gridContainer = this.down('#grids' + gridSide);
      // new grid with store data
      var grid = Ext.create('Rally.ui.grid.Grid', {
        xtype: 'rallygrid',
        title: myTitle,
        columnCfgs: myColumns,
        storeConfig: {
          model: myModel,
          context: this.context.getDataContext(),
          autoLoad: {start: 0, limit: pageSize},
          filters: myFilters()
        }
      });
      // show me the grid!
      gridContainer.add(grid);

      /*
      var blockedStoriesStore = Ext.create('Rally.data.wsapi.Store', {
        model: 'UserStory',
        context: this.context.getDataContext(),
        autoLoad: {start: 0, limit: 5},
        filters: [
          {
            property: 'blocked',
            operator: '=',
            value: 'true'
          }
        ],
        listeners: {
          load: function(myStore, data) {
            console.log('got', data);
            var blockedStoriesGrid = Ext.create('Rally.ui.grid.Grid', {
              title: 'Blocked Stories',
              columnCfgs: ['FormattedID', 'Name', 'Owner'],
              store: myStore
            });
            var ls = this.down('#leftGrids');
            ls.add(blockedStoriesGrid);

          },
          scope: this
        }
      });
      */
    }

});
<|endoftext|>


<commit_before>const async = require("async")
,   notification = require('./notification')
,   w3ciprcheck = require('./w3c-ipr')
,   doAsync = require('doasync') // rather than utils.promisy to get "free" support for object methods
,   w3c = require("node-w3capi");

let store, log;

async function findW3CUserFromGithub(user) {
  log.info("Looking for github user with id " + user.ghID + " in W3C API");
  try {
    // can't easily promisify w3c API :(
    let w3cuser = await new Promise((res, rej) => w3c.user({type: 'github', id: user.ghID}).fetch((err, u) => { if (err) return rej(err); return res(u);}));
    log.info(JSON.stringify(w3cuser, null, 2));
    await doAsync(store).mergeOnUser(user.username, {
      w3cid:  w3cuser.id,
      w3capi: w3cuser._links.self.href.replace(/.*\//, "")
    });
  } catch (err) {
    return user;
  }
  log.info("Found matching W3C user");
  return doAsync(store).getUser(user.username);
}

async function findOrCreateUserFromGithub(username, gh) {
  let user;
  try {
    user = await doAsync(store).getUser(username);
  } catch (err) {
    if (err.error !== "not_found") throw err;
  }
  if (!user) {
    log.info("Getting GH id from github for " + username);
    let ghuser = await gh.getUser(username);
    // we store this for sake of efficiency
    await doAsync(store).addUser(ghuser);
    return findW3CUserFromGithub(ghuser);
  } else {
    // Let's check if the link has since been established
    if (!user.w3capi) {
      return findW3CUserFromGithub(user);
    } else {
      return user;
    }
  }
}

async function getStoredPR(fullname) {
  log.info("Setting status for PR " + fullname);
  let repo = await doAsync(store).getRepo(fullname);
  if (!repo) throw ("Unknown repository: " + fullname);
  let token = await doAsync(store).getToken(repo.owner);
  if (!token) throw ("Token not found for: " + repo.owner);
  return {repoGroups: repo.groups, token};
}

async function updateStoredPR(pr) {
  log.info("Setting status for PR " + pr.fullName);
  let updatedPr = await doAsync(store).updatePR(pr.fullName, pr.num, pr);
  return updatedPr;
}

async function setGhStatus(gh, status) {
  try {
    await doAsync(gh).status(status);
  } catch(err) {
    log.error(err);
  }
}

async function checkPrScope(gh, pr) {
  const ignoreFiles = ["package.json", "package-lock.json", ".travis.yml", "w3c.json", "CONTRIBUTING.md", "LICENSE.md", "CODE_OF_CONDUCT.md"];
  const ignorePath = ".github/";
  let files;
  try {
    ({items: files} = await gh.getPrFiles(pr.owner, pr.shortName, pr.num));
  } catch(err) {
    log.error(err);
    // if unsure, assumes it is IPR-relevant
    return true;
  }
  return !(files.map(f => f.filename).every(p => ignoreFiles.includes(p) || p.startsWith(ignorePath)));
}

function prChecker(config, argLog, argStore, GH, mailer) {
  log = argLog;
  store = argStore;
  return {
    validate: async function prStatus (pr, delta, cb) {
      const currentPrAcceptability = pr.acceptable;
      const prString = pr.owner + "/" + pr.shortName + "/" + pr.num;
      const statusData = {
        owner:      pr.owner,
        shortName:  pr.shortName,
        sha:        pr.sha,
        payload:    {
          state:          "pending",
          target_url:     config.url + "pr/id/" + prString,
          description:    "PR is being assessed, results will come shortly.",
          context:        "ipr"
        }
      };
      let token, repoGroups, iprRelevant = true;
      try {
        ({token, repoGroups} = await getStoredPR(pr.fullName));
      } catch (err) {
        return cb(err);
      }
      const gh = new GH({ accessToken: token.token });
      log.info("Setting pending status on " + prString);
      await setGhStatus(gh, statusData);

      iprRelevant = await checkPrScope(gh, pr, log);
      if (!iprRelevant) {
        statusData.payload.state = "success";
        statusData.payload.description = "PR files identified as non-substantive.";
        log.info("Setting status success for " + prString);
        pr.acceptable = "yes";
        await setGhStatus(gh, statusData);
        try {
          let updatedPR = await updateStoredPR(pr);
          return cb(null, updatedPR);
        } catch (err) {
          return cb(err);
        }
      }

      if (pr.markedAsNonSubstantiveBy) {
        pr.acceptable = "yes";
        statusData.payload.state = "success";
        statusData.payload.description = "PR deemed acceptable as non-substantive by @" + pr.markedAsNonSubstantiveBy + ".";
        log.info("Setting status success for " + prString);
        await setGhStatus(gh, statusData);
        try {
          let updatedPR = await updateStoredPR(pr);
          return cb(null, updatedPR);
        } catch (err) {
          return cb(err);
        }
      }

      log.info("Looking up users for " + prString);
      let contrib = {};
      log.info("Finding deltas for " + prString);
      pr.contributors.forEach(function (c) { contrib[c] = true; });
      delta.add.forEach(function (c) { contrib[c] = true; });
      delta.remove.forEach(function (c) { delete contrib[c]; });
      pr.contributors = Object.keys(contrib);
      pr.contribStatus = {};
      pr.groups = repoGroups;
      pr.affiliations = {};
      let results = await Promise.all(
        pr.contributors.map(async function(username) {
          let user = await findOrCreateUserFromGithub(username, gh);
          // TODO: check that this is appropriate
          // and if so, replace by check of affiliation
          // to staff
          if (user.blanket) {
            pr.affiliations[user.affiliation] = user.affiliationName;
            pr.contribStatus[username] = "ok";
            return "ok";
          }
          // if user not found in W3C API,
          // report undetermined affiliation
          // TODO: We will contact contributor to ask
          // establishing the connection.
          if (!user.w3capi) {
            pr.contribStatus[username] = "undetermined affiliation";
            return "undetermined affiliation";
          }
          let result = await w3ciprcheck(w3c, user.w3capi, user.displayName, repoGroups, store);
          let ok = result.ok;
          if (ok) {
            pr.affiliations[result.affiliation.id] = result.affiliation.name;
            pr.contribStatus[username] = "ok";
            return "ok";
          } else {
            // we assume that all groups are of the same type
            let group = await doAsync(store).getGroup(repoGroups[0]);
            if (!group) throw "Unknown group: " + repoGroups[0];
            if (group.groupType === 'WG') {
              log.info("Looking up for non-participant licensing contribution");
              if (pr.repoId) {
                let nplc;
                try {
                  nplc = await new Promise((res, rej) => w3c.nplc({repoId: pr.repoId, pr: pr.num}).fetch((err, n) => { if (err) return rej(err); return res(n);}));
                } catch (err) {
                  // Non-participant licensing contribution doesn't exist yet
                  pr.contribStatus[username] = "no commitment made";
                  return "no commitment made";
                }
                const u = nplc.commitments.find(c => c.user["connected-accounts"].find(ca => ca.nickname === username));
                const contribStatus = (u.commitment_date === undefined) ? "commitment pending" : "ok";
                pr.contribStatus[username] = contribStatus;
                return contribStatus;
              } else {
                pr.contribStatus[username] = "no commiment made - missing repository ID";
                return "no commiment made - missing repository ID";
              }
            } else {
              pr.contribStatus[username] = "not in group";
              return "not in group";
            }
          }
        }));
      let good = results.every(st => st === "ok");
      log.info("Got users for " + prString + " results good? " + good);
      if (good) {
        pr.acceptable = "yes";
        pr.unknownUsers = [];
        pr.outsideUsers = [];
        statusData.payload.state = "success";
        statusData.payload.description = "PR deemed acceptable.";
        log.info("Setting status success for " + prString);
        await setGhStatus(gh, statusData);
        let updatedPR = await updateStoredPR(pr);
        return cb(null, updatedPR);
      }
      pr.acceptable = "no";
      pr.unknownUsers = [];
      pr.outsideUsers = [];
      pr.unaffiliatedUsers = [];
      for (var u in pr.contribStatus) {
        if (pr.contribStatus[u] === "unknown") pr.unknownUsers.push(u);
        if (pr.contribStatus[u] === "undetermined affiliation") pr.unaffiliatedUsers.push(u);
        if (pr.contribStatus[u] === "not in group") pr.outsideUsers.push(u);
      }
      var msg = "PR has contribution issues.";
      const collateUserNames = users => users.map(u => "@" + u).join (", ");
      if (pr.unknownUsers.length)
        msg += " The following users were unknown: " + collateUserNames(pr.unknownUsers) +
        ".";
      if (pr.unaffiliatedUsers.length)
        msg += " The following users' affiliation could not be determined: " + collateUserNames(pr.unaffiliatedUsers) + ".";
      if (pr.outsideUsers.length)
        msg += " The following users were not in the repository's groups: " + collateUserNames(pr.outsideUsers) + ".";
      statusData.payload.state = "failure";
      statusData.payload.description =  msg;
      if (statusData.payload.description.length > 140) {
        statusData.payload.description = statusData.payload.description.slice(0, 139) + '…';
      }
      log.info("Setting status failure for " + prString + ", " + msg);
      await setGhStatus(gh, statusData);
      let updatedPR = await updateStoredPR(pr);
      // Only send email notifications
      // if the status of the PR has just
      // changed
      if (currentPrAcceptability !== pr.acceptable) {
        // FIXME: make it less context-dependent
        await notification.notifyContacts(gh, pr, statusData, mailer, {from: config.notifyFrom, fallback: config.emailFallback || [], cc: config.emailCC || []}, store, log);
        return cb(null, updatedPR);
      }
      return cb(null, updatedPR);
    }
  };
}

module.exports = prChecker;
<commit_msg>Add LICENSE.txt to list of files to ignore<commit_after>const async = require("async")
,   notification = require('./notification')
,   w3ciprcheck = require('./w3c-ipr')
,   doAsync = require('doasync') // rather than utils.promisy to get "free" support for object methods
,   w3c = require("node-w3capi");

let store, log;

async function findW3CUserFromGithub(user) {
  log.info("Looking for github user with id " + user.ghID + " in W3C API");
  try {
    // can't easily promisify w3c API :(
    let w3cuser = await new Promise((res, rej) => w3c.user({type: 'github', id: user.ghID}).fetch((err, u) => { if (err) return rej(err); return res(u);}));
    log.info(JSON.stringify(w3cuser, null, 2));
    await doAsync(store).mergeOnUser(user.username, {
      w3cid:  w3cuser.id,
      w3capi: w3cuser._links.self.href.replace(/.*\//, "")
    });
  } catch (err) {
    return user;
  }
  log.info("Found matching W3C user");
  return doAsync(store).getUser(user.username);
}

async function findOrCreateUserFromGithub(username, gh) {
  let user;
  try {
    user = await doAsync(store).getUser(username);
  } catch (err) {
    if (err.error !== "not_found") throw err;
  }
  if (!user) {
    log.info("Getting GH id from github for " + username);
    let ghuser = await gh.getUser(username);
    // we store this for sake of efficiency
    await doAsync(store).addUser(ghuser);
    return findW3CUserFromGithub(ghuser);
  } else {
    // Let's check if the link has since been established
    if (!user.w3capi) {
      return findW3CUserFromGithub(user);
    } else {
      return user;
    }
  }
}

async function getStoredPR(fullname) {
  log.info("Setting status for PR " + fullname);
  let repo = await doAsync(store).getRepo(fullname);
  if (!repo) throw ("Unknown repository: " + fullname);
  let token = await doAsync(store).getToken(repo.owner);
  if (!token) throw ("Token not found for: " + repo.owner);
  return {repoGroups: repo.groups, token};
}

async function updateStoredPR(pr) {
  log.info("Setting status for PR " + pr.fullName);
  let updatedPr = await doAsync(store).updatePR(pr.fullName, pr.num, pr);
  return updatedPr;
}

async function setGhStatus(gh, status) {
  try {
    await doAsync(gh).status(status);
  } catch(err) {
    log.error(err);
  }
}

async function checkPrScope(gh, pr) {
  const ignoreFiles = ["package.json", "package-lock.json", ".travis.yml", "w3c.json", "CONTRIBUTING.md", "LICENSE.md", "LICENSE.txt", "CODE_OF_CONDUCT.md"];
  const ignorePath = ".github/";
  let files;
  try {
    ({items: files} = await gh.getPrFiles(pr.owner, pr.shortName, pr.num));
  } catch(err) {
    log.error(err);
    // if unsure, assumes it is IPR-relevant
    return true;
  }
  return !(files.map(f => f.filename).every(p => ignoreFiles.includes(p) || p.startsWith(ignorePath)));
}

function prChecker(config, argLog, argStore, GH, mailer) {
  log = argLog;
  store = argStore;
  return {
    validate: async function prStatus (pr, delta, cb) {
      const currentPrAcceptability = pr.acceptable;
      const prString = pr.owner + "/" + pr.shortName + "/" + pr.num;
      const statusData = {
        owner:      pr.owner,
        shortName:  pr.shortName,
        sha:        pr.sha,
        payload:    {
          state:          "pending",
          target_url:     config.url + "pr/id/" + prString,
          description:    "PR is being assessed, results will come shortly.",
          context:        "ipr"
        }
      };
      let token, repoGroups, iprRelevant = true;
      try {
        ({token, repoGroups} = await getStoredPR(pr.fullName));
      } catch (err) {
        return cb(err);
      }
      const gh = new GH({ accessToken: token.token });
      log.info("Setting pending status on " + prString);
      await setGhStatus(gh, statusData);

      iprRelevant = await checkPrScope(gh, pr, log);
      if (!iprRelevant) {
        statusData.payload.state = "success";
        statusData.payload.description = "PR files identified as non-substantive.";
        log.info("Setting status success for " + prString);
        pr.acceptable = "yes";
        await setGhStatus(gh, statusData);
        try {
          let updatedPR = await updateStoredPR(pr);
          return cb(null, updatedPR);
        } catch (err) {
          return cb(err);
        }
      }

      if (pr.markedAsNonSubstantiveBy) {
        pr.acceptable = "yes";
        statusData.payload.state = "success";
        statusData.payload.description = "PR deemed acceptable as non-substantive by @" + pr.markedAsNonSubstantiveBy + ".";
        log.info("Setting status success for " + prString);
        await setGhStatus(gh, statusData);
        try {
          let updatedPR = await updateStoredPR(pr);
          return cb(null, updatedPR);
        } catch (err) {
          return cb(err);
        }
      }

      log.info("Looking up users for " + prString);
      let contrib = {};
      log.info("Finding deltas for " + prString);
      pr.contributors.forEach(function (c) { contrib[c] = true; });
      delta.add.forEach(function (c) { contrib[c] = true; });
      delta.remove.forEach(function (c) { delete contrib[c]; });
      pr.contributors = Object.keys(contrib);
      pr.contribStatus = {};
      pr.groups = repoGroups;
      pr.affiliations = {};
      let results = await Promise.all(
        pr.contributors.map(async function(username) {
          let user = await findOrCreateUserFromGithub(username, gh);
          // TODO: check that this is appropriate
          // and if so, replace by check of affiliation
          // to staff
          if (user.blanket) {
            pr.affiliations[user.affiliation] = user.affiliationName;
            pr.contribStatus[username] = "ok";
            return "ok";
          }
          // if user not found in W3C API,
          // report undetermined affiliation
          // TODO: We will contact contributor to ask
          // establishing the connection.
          if (!user.w3capi) {
            pr.contribStatus[username] = "undetermined affiliation";
            return "undetermined affiliation";
          }
          let result = await w3ciprcheck(w3c, user.w3capi, user.displayName, repoGroups, store);
          let ok = result.ok;
          if (ok) {
            pr.affiliations[result.affiliation.id] = result.affiliation.name;
            pr.contribStatus[username] = "ok";
            return "ok";
          } else {
            // we assume that all groups are of the same type
            let group = await doAsync(store).getGroup(repoGroups[0]);
            if (!group) throw "Unknown group: " + repoGroups[0];
            if (group.groupType === 'WG') {
              log.info("Looking up for non-participant licensing contribution");
              if (pr.repoId) {
                let nplc;
                try {
                  nplc = await new Promise((res, rej) => w3c.nplc({repoId: pr.repoId, pr: pr.num}).fetch((err, n) => { if (err) return rej(err); return res(n);}));
                } catch (err) {
                  // Non-participant licensing contribution doesn't exist yet
                  pr.contribStatus[username] = "no commitment made";
                  return "no commitment made";
                }
                const u = nplc.commitments.find(c => c.user["connected-accounts"].find(ca => ca.nickname === username));
                const contribStatus = (u.commitment_date === undefined) ? "commitment pending" : "ok";
                pr.contribStatus[username] = contribStatus;
                return contribStatus;
              } else {
                pr.contribStatus[username] = "no commiment made - missing repository ID";
                return "no commiment made - missing repository ID";
              }
            } else {
              pr.contribStatus[username] = "not in group";
              return "not in group";
            }
          }
        }));
      let good = results.every(st => st === "ok");
      log.info("Got users for " + prString + " results good? " + good);
      if (good) {
        pr.acceptable = "yes";
        pr.unknownUsers = [];
        pr.outsideUsers = [];
        statusData.payload.state = "success";
        statusData.payload.description = "PR deemed acceptable.";
        log.info("Setting status success for " + prString);
        await setGhStatus(gh, statusData);
        let updatedPR = await updateStoredPR(pr);
        return cb(null, updatedPR);
      }
      pr.acceptable = "no";
      pr.unknownUsers = [];
      pr.outsideUsers = [];
      pr.unaffiliatedUsers = [];
      for (var u in pr.contribStatus) {
        if (pr.contribStatus[u] === "unknown") pr.unknownUsers.push(u);
        if (pr.contribStatus[u] === "undetermined affiliation") pr.unaffiliatedUsers.push(u);
        if (pr.contribStatus[u] === "not in group") pr.outsideUsers.push(u);
      }
      var msg = "PR has contribution issues.";
      const collateUserNames = users => users.map(u => "@" + u).join (", ");
      if (pr.unknownUsers.length)
        msg += " The following users were unknown: " + collateUserNames(pr.unknownUsers) +
        ".";
      if (pr.unaffiliatedUsers.length)
        msg += " The following users' affiliation could not be determined: " + collateUserNames(pr.unaffiliatedUsers) + ".";
      if (pr.outsideUsers.length)
        msg += " The following users were not in the repository's groups: " + collateUserNames(pr.outsideUsers) + ".";
      statusData.payload.state = "failure";
      statusData.payload.description =  msg;
      if (statusData.payload.description.length > 140) {
        statusData.payload.description = statusData.payload.description.slice(0, 139) + '…';
      }
      log.info("Setting status failure for " + prString + ", " + msg);
      await setGhStatus(gh, statusData);
      let updatedPR = await updateStoredPR(pr);
      // Only send email notifications
      // if the status of the PR has just
      // changed
      if (currentPrAcceptability !== pr.acceptable) {
        // FIXME: make it less context-dependent
        await notification.notifyContacts(gh, pr, statusData, mailer, {from: config.notifyFrom, fallback: config.emailFallback || [], cc: config.emailCC || []}, store, log);
        return cb(null, updatedPR);
      }
      return cb(null, updatedPR);
    }
  };
}

module.exports = prChecker;
<|endoftext|>


<commit_before>var Poloniex = require("poloniex.js");
var util = require('../core/util.js');
var _ = require('lodash');
var moment = require('moment');
var log = require('../core/log');

// Helper methods
function joinCurrencies(currencyA, currencyB){
    return currencyA + '_' + currencyB;
}

var Trader = function(config) {
  _.bindAll(this);
  if(_.isObject(config)) {
    this.key = config.key;
    this.secret = config.secret;
    this.clientID = config.username;
    this.currency = config.currency;
    this.asset = config.asset;
  }
  this.name = 'Poloniex';
  this.balance;
  this.price;

  this.poloniex = new Poloniex(this.key, this.secret);
}

// if the exchange errors we try the same call again after
// waiting 10 seconds
Trader.prototype.retry = function(method, args) {
  var wait = +moment.duration(10, 'seconds');
  log.debug(this.name, 'returned an error, retrying..');

  var self = this;

  // make sure the callback (and any other fn)
  // is bound to Trader
  _.each(args, function(arg, i) {
    if(_.isFunction(arg))
      args[i] = _.bind(arg, self);
  });

  // run the failed method again with the same
  // arguments after wait
  setTimeout(
    function() { method.apply(self, args) },
    wait
  );
}

Trader.prototype.getPortfolio = function(callback) {
  var set = function(err, data) {
    var portfolio = [];
    _.each(data, function(amount, asset) {
      if(asset.indexOf('available') !== -1) {
        //asset = asset.substr(0, 3).toUpperCase();
        portfolio.push({name: asset, amount: parseFloat(amount)});
      }
    });
    callback(err, portfolio);
  }
  this.poloniex.myBalances(_.bind(set, this));
}

Trader.prototype.getTicker = function(callback) {
  this.poloniex.getTicker(callback);
}

Trader.prototype.getFee = function(callback) {
  var set = function(err, data) {
    if(err)
      callback(err);

    callback(false, parseFloat(data.takerFee));
  }
  this.poloniex._private('returnFeeInfo', _.bind(set, this));
}

Trader.prototype.buy = function(amount, price, callback) {
  var set = function(err, result) {
    if(err || result.error)
      return log.error('unable to buy:', err, result);

    callback(null, result.orderNumber);
  };

  this.poloniex.buy(this.currency, this.asset, price, amount, _.bind(set, this));
}

Trader.prototype.sell = function(amount, price, callback) {
  var set = function(err, result) {
    if(err || result.error)
      return log.error('unable to sell:', err, result);

    callback(null, result.orderNumber);
  };

  this.poloniex.sell(this.currency, this.asset, price, amount, _.bind(set, this));
}

Trader.prototype.checkOrder = function(order, callback) {
  var check = function(err, result) {
    var stillThere = _.find(result, function(o) { return o.orderNumber === order });
    callback(err, !stillThere);
  };

  this.poloniex.myOpenOrders(_.bind(check, this));
}

Trader.prototype.cancelOrder = function(order, callback) {
  var cancel = function(err, result) {
    if(err || !result.success) {
      log.error('unable to cancel order', order, '(', err, result, ')');
      // return this.retry(this.cancelOrder, args);
    }
  };

  this.poloniex.cancelOrder(this.currency, this.asset, order, _.bind(cancel, this));
}

Trader.prototype.getTrades = function(since, callback, descending) {
  var args = _.toArray(arguments);
  var process = function(err, result) {
    if(err) {
      return this.retry(this.getTrades, args);
    }

    result = _.map(result, function(trade) {
    	return {
        date: moment.utc(trade.date).format('X'),
        price: trade.rate,
        tid: trade.tradeID
      };
    });

    callback(null, result.reverse());
  };

  var params = {
    currencyPair: joinCurrencies(this.currency, this.asset)
  }
  if (since)
    params.start = _.isNumber(since) ? since : since.format('X');

  this.poloniex._public('returnTradeHistory', params, _.bind(process, this));
}

module.exports = Trader;
<commit_msg>relay trade amount at poloniex, fix #325<commit_after>var Poloniex = require("poloniex.js");
var util = require('../core/util.js');
var _ = require('lodash');
var moment = require('moment');
var log = require('../core/log');

// Helper methods
function joinCurrencies(currencyA, currencyB){
    return currencyA + '_' + currencyB;
}

var Trader = function(config) {
  _.bindAll(this);
  if(_.isObject(config)) {
    this.key = config.key;
    this.secret = config.secret;
    this.clientID = config.username;
    this.currency = config.currency;
    this.asset = config.asset;
  }
  this.name = 'Poloniex';
  this.balance;
  this.price;

  this.poloniex = new Poloniex(this.key, this.secret);
}

// if the exchange errors we try the same call again after
// waiting 10 seconds
Trader.prototype.retry = function(method, args) {
  var wait = +moment.duration(10, 'seconds');
  log.debug(this.name, 'returned an error, retrying..');

  var self = this;

  // make sure the callback (and any other fn)
  // is bound to Trader
  _.each(args, function(arg, i) {
    if(_.isFunction(arg))
      args[i] = _.bind(arg, self);
  });

  // run the failed method again with the same
  // arguments after wait
  setTimeout(
    function() { method.apply(self, args) },
    wait
  );
}

Trader.prototype.getPortfolio = function(callback) {
  var set = function(err, data) {
    var portfolio = [];
    _.each(data, function(amount, asset) {
      if(asset.indexOf('available') !== -1) {
        //asset = asset.substr(0, 3).toUpperCase();
        portfolio.push({name: asset, amount: parseFloat(amount)});
      }
    });
    callback(err, portfolio);
  }
  this.poloniex.myBalances(_.bind(set, this));
}

Trader.prototype.getTicker = function(callback) {
  this.poloniex.getTicker(callback);
}

Trader.prototype.getFee = function(callback) {
  var set = function(err, data) {
    if(err)
      callback(err);

    callback(false, parseFloat(data.takerFee));
  }
  this.poloniex._private('returnFeeInfo', _.bind(set, this));
}

Trader.prototype.buy = function(amount, price, callback) {
  var set = function(err, result) {
    if(err || result.error)
      return log.error('unable to buy:', err, result);

    callback(null, result.orderNumber);
  };

  this.poloniex.buy(this.currency, this.asset, price, amount, _.bind(set, this));
}

Trader.prototype.sell = function(amount, price, callback) {
  var set = function(err, result) {
    if(err || result.error)
      return log.error('unable to sell:', err, result);

    callback(null, result.orderNumber);
  };

  this.poloniex.sell(this.currency, this.asset, price, amount, _.bind(set, this));
}

Trader.prototype.checkOrder = function(order, callback) {
  var check = function(err, result) {
    var stillThere = _.find(result, function(o) { return o.orderNumber === order });
    callback(err, !stillThere);
  };

  this.poloniex.myOpenOrders(_.bind(check, this));
}

Trader.prototype.cancelOrder = function(order, callback) {
  var cancel = function(err, result) {
    if(err || !result.success) {
      log.error('unable to cancel order', order, '(', err, result, ')');
      // return this.retry(this.cancelOrder, args);
    }
  };

  this.poloniex.cancelOrder(this.currency, this.asset, order, _.bind(cancel, this));
}

Trader.prototype.getTrades = function(since, callback, descending) {
  var args = _.toArray(arguments);
  var process = function(err, result) {
    if(err) {
      return this.retry(this.getTrades, args);
    }

    result = _.map(result, function(trade) {
    	return {
        tid: trade.tradeID,
        amount: +trade.amount,
        date: moment.utc(trade.date).format('X'),
        price: +trade.rate
      };
    });

    callback(null, result.reverse());
  };

  var params = {
    currencyPair: joinCurrencies(this.currency, this.asset)
  }
  if (since)
    params.start = _.isNumber(since) ? since : since.format('X');

  this.poloniex._public('returnTradeHistory', params, _.bind(process, this));
}

module.exports = Trader;
<|endoftext|>


<commit_before>//= require zepto

$(function($) {

  var $document = $(document),
    $rootNode   = $('html, body'),
    $footer     = $('footer');

  $footer.find('.copyright-year').html(new Date().getFullYear());

  Itch.attachBuyButton(document.getElementById("buy_button"), {
    // replace the following with the correct information about your game
    user: "madcapacity",
    game: "astervoid-2000"
  });

  Itch.getGameData({
    user: "madcapacity",
    game: "astervoid-2000",
    secret: "",
    onComplete: function(data) {
        var numKeysDiv = document.getElementById("claimed-numbers");
        var priceDiv   = document.getElementById("buy_price");
        var keysRemainingSpan = document.getElementById("claimed_keys_remaining");
        var reward     = data.rewards && data.rewards[0];

        if ( reward ) {
            var total = 94 + reward.amount;
            var amountRemaining = total - reward.amount_remaining;
            numKeysDiv.innerText = amountRemaining + " / " + total;

            priceDiv.innerText = reward.price.substr(0,2);
            keysRemainingSpan.innerText = total - amountRemaining;
        }
        else {
            numKeysDiv.innerText = "Rewards missing";
        }
    }
  });

});
<commit_msg>adjust # of keys provided<commit_after>//= require zepto

$(function($) {

  var $document = $(document),
    $rootNode   = $('html, body'),
    $footer     = $('footer');

  $footer.find('.copyright-year').html(new Date().getFullYear());

  Itch.attachBuyButton(document.getElementById("buy_button"), {
    // replace the following with the correct information about your game
    user: "madcapacity",
    game: "astervoid-2000"
  });

  Itch.getGameData({
    user: "madcapacity",
    game: "astervoid-2000",
    secret: "",
    onComplete: function(data) {
        var numKeysDiv = document.getElementById("claimed-numbers");
        var priceDiv   = document.getElementById("buy_price");
        var keysRemainingSpan = document.getElementById("claimed_keys_remaining");
        var reward     = data.rewards && data.rewards[0];

        if ( reward ) {
            // 97 is the # provided to internal alpha testers, press, etc
            var total = 97 + reward.amount;
            var amountRemaining = total - reward.amount_remaining;
            numKeysDiv.innerText = amountRemaining + " / " + total;

            priceDiv.innerText = reward.price.substr(0,2);
            keysRemainingSpan.innerText = total - amountRemaining;
        }
        else {
            numKeysDiv.innerText = "Rewards missing";
        }
    }
  });

});
<|endoftext|>


<commit_before>var CursorMaintainer = (function () {
  'use strict';

  var layer,          // A statistical approach configured per format.
      retrospective,  // Format-independent statistical cursor maintenance.
      costFunctions;  // Cost functions for the retrospective approach.

  //--- Meta approach: entails reimplementing the format with
  //  elementary operations on a text-with-cursor object.

  function TextWithCursor(text, cursor) {
    this.text = text || '';
    this.cursor = cursor || 0;
  }

  TextWithCursor.prototype.length = function () {
    return this.text.length;
  };

  // TextCursor.read gets one character by default, several if specified.
  TextWithCursor.prototype.read = function (begin, length) {
    if (length === undefined) {
      length = 1;
    }
    if (length <= 0) {
      return;
    }
    return this.text.substring(begin, begin + length);
  };

  // TextWithCursor.insert adds text at the specified insertion point and
  //  shifts the cursor if needed. The cursor is unchanged if it is at or to
  //  the left of the insertion point.
  TextWithCursor.prototype.insert = function (begin, subtext) {
    this.text = this.text.substring(0, begin) + subtext +
        this.text.substring(begin);
    if (this.cursor > begin) {
      this.cursor += subtext.length;
    }
  };

  // TextWithCursor.delete removes one character by default, several if
  //  specified, then shifts the cursor if needed. The cursor is unchanged
  //  if it is at or to the left of the deleted characters.
  TextWithCursor.prototype.delete = function (begin, length) {
    if (length === undefined) {
      length = 1;
    }
    if (length <= 0) {
      return;
    }
    this.text = this.text.substring(0, begin) +
        this.text.substring(begin + length);
    if (this.cursor > begin) {
      this.cursor -= Math.min(this.cursor - begin, length);
    }
  };

  //--- Layer approach: a statistical approach that looks at layers of text
  //  induced by character sets specified for a format. Within each layer of
  //  the formatted text, we seek the cursor position where the proportion
  //  of ratio characters to the left is closest to the equivalent proportion
  //  at the raw cursor position in the raw text. If several cursor positions
  //  are equally close, we move to the next layer. If a tie-breaker is
  //  needed, we take either the left or right end of the final range,
  //  as configured for the format.

  layer = {};

  // layer.makeMaintainer returns a function that takes an instance of the
  //  cursor-maintenance problem and returns an object containing the
  //  cursor position computed by the layer approach. The resulting cursor
  //  maintainer does not check for equality of the formatted text and
  //  raw text. The caller should perform this check and only call the
  //  cursor maintainer if the formatted text differs from the raw text.
  // testers: An array of objects, each with a test() method that takes a
  //  character and returns a Boolean value. A regex is such an object.
  // preferRight: A Boolean value. If this argument is omitted, ties are
  //  broken to the left, i.e., preferRight is false by default.
  layer.makeMaintainer = function (testers, preferRight) {
    return function (raw, cursor, formatted) {
      var rawCount, rawTotal, rawRatio,
          formattedCounts, formattedTotal, formattedRatio,
          delta, bestDelta, bestFormattedRatio,
          rank, tester, pos,
          left = 0,
          right = formatted.length,
          bestLeft = left, bestRight = right;
      if (right == 0) {
        return { text: '', cursor: 0 };
      }
      for (rank = 0; rank < testers.length; ++rank) {
        tester = testers[rank];
        // Scan the raw text, counting current layer characters.
        rawCount = 0;
        rawTotal = 0;
        for (pos = 0; pos < raw.length; ++pos) {
          if (tester.test(raw.charAt(pos))) {
            ++rawTotal;
            // We only need the ratio for the raw cursor position.
            if (pos < cursor) {
              ++rawCount;
            }
          }
        }
        // Bail out if the raw text has no layer characters.
        if (rawTotal == 0) {
          continue;
        }
        // Compute the raw cursor's ratio in the current layer.
        rawRatio = rawCount / rawTotal;
        // Scan the formatted text and store the layer count at each position.
        formattedCounts = new Array(formatted.length + 1);
        formattedCounts[0] = 0;
        formattedTotal = 0;
        for (pos = 0; pos < formatted.length; ++pos) {
          if (tester.test(formatted.charAt(pos))) {
            ++formattedTotal;
          }
          formattedCounts[pos + 1] = formattedTotal;
        }
        // Bail out if the formatted text has no layer characters.
        if (formattedTotal == 0) {
          continue;
        }
        // Scan the layer counts to compute the ratio at each cursor position.
        // Keep track of the closest ratio and the indices where it occurs.
        bestFormattedRatio = formattedCounts[left] / formattedTotal;
        bestDelta = Math.abs(rawRatio - bestFormattedRatio);
        bestLeft = bestRight = left;
        for (pos = left + 1; pos <= right; ++pos) {
          formattedRatio = formattedCounts[pos] / formattedTotal;
          delta = Math.abs(rawRatio - formattedRatio);
          if (delta == bestDelta) {
            bestRight = pos;
          } else if (delta < bestDelta) {
            bestFormattedRatio = formattedRatio;
            bestDelta = delta;
            bestLeft = bestRight = pos;
          }
        }
        if (bestLeft == bestRight) {
          break;
        }
        // If there is a tie, shrink the range for the next layer.
        left = bestLeft;
        right = bestRight;
      }
      if (preferRight) {
        return { cursor: bestRight };
      }
      return { cursor: bestLeft };
    };
  };

  // layer.augmentFormat returns a cursor-maintaining formatter based on a
  //  given format. A cursor-maintaining formatter takes raw text and a raw
  //  cursor position; it returns formatted text and a new cursor position.
  // format: A function that takes raw text and returns formatted text.
  //  testers, preferRight: arguments to layer.makeMaintainer.
  layer.augmentFormat = function (format, testers, preferRight) {
    var maintainer = layer.makeMaintainer(testers, preferRight);
    return function (raw, cursor) {
      var formatted = format(raw),
          result = maintainer(raw, cursor, formatted);
      result.text = formatted;
      return result;
    };
  };

  //--- Retrospective approach: A statistical approach that is not configured
  //  for any particular format. Instead, we configure it with a cost function
  //  that computes a score for every position in the formatted text. The
  //  position with the lowest score is chosen as the new cursor position.
  //  If several positions are in a tie, the leftmost is chosen.

  costFunctions = {};
  retrospective = { costFunctions: costFunctions };

  // levenshtein implements the well-known Levenshtein distance. It is
  //  called by the costFunctions.splitLevenshtein.
  function levenshtein(s, t) {
    var n = s.length,
        m = t.length,
        current,
        previous,
        temp,
        i, j;
    if (Math.min(n, m) == 0) {
      return Math.max(n, m);
    }
    current = new Array(m + 1);
    for (i = 0; i <= m; ++i) {
      current[i] = i;
    }
    previous = new Array(m + 1);
    for (i = 1; i <= n; ++i) {
      temp = current;
      current = previous;
      previous = temp;
      current[0] = previous[0] + 1;
      for (j = 1; j <= m; ++j) {
        if (t.charAt(j - 1) == s.charAt(i - 1)) {
          current[j] = previous[j - 1];
        } else {
          current[j] = Math.min(previous[j - 1] + 1,
                                previous[j] + 1,
                                current[j - 1] + 1);
        }
      }
    }
    return current[m];
  }

  // costFunctions.splitLevenshtein splits the raw text and formatted text
  //  at respective cursor positions, computes the Levenshtein distance
  //  between the left parts, then between the right parts, and takes the
  //  sum. The raw cursor position is passed to this function. It is compared
  //  to all positions in the formatted text, and the resulting scores
  //  are returned in an array.
  costFunctions.splitLevenshtein = function (s, sCursor, t) {
    var tCursor,
        left, right,
        scores = new Array(t.length + 1);
    for (tCursor = 0; tCursor <= t.length; ++tCursor) {
      left = levenshtein(s.substring(0, sCursor), t.substring(0, tCursor));
      right = levenshtein(s.substring(sCursor), t.substring(tCursor));
      scores[tCursor] = left + right;
    }
    return scores;
  };

  // getCommonChars finds the set of characters that appear in both of the
  //  given strings. It is called by costFunctions.frequencyRatios.
  function getCommonChars(s, t) {
    var sCharSet = {},
        tCharSet = {},
        chars = [],
        i;
    for (i = 0; i < s.length; ++i) {
      sCharSet[s.charAt(i)] = true;
    }
    for (i = 0; i < t.length; ++i) {
      tCharSet[t.charAt(i)] = true;
    }
    Object.keys(sCharSet).forEach(function (ch) {
      if (ch in tCharSet) {
        chars.push(ch);
      }
    });
    return chars;
  }

  // getLeftCounts is given an arbitrary string and an array containing a
  //  set of characters. It returns an array containing, for each position
  //  in the string, an object that maps each character in the set to the
  //  number of times the character appears to the left of the position.
  function getLeftCounts(s, chars) {
    var counts = new Array(s.length + 1),
        pos, i, ch;
    // Initialize all character frequencies to zero.
    counts[0] = {};
    for (i = 0; i < chars.length; ++i) {
      counts[0][chars[i]] = 0;
    }
    // Iterate over string positions, dealing with set characters in parallel.
    for (pos = 1; pos <= s.length; ++pos) {
      counts[pos] = {};
      // Copy the previous character frequencies.
      for (i = 0; i < chars.length; ++i) {
        counts[pos][chars[i]] = counts[pos - 1][chars[i]];
      }
      // Update the frequency of one character at the most.
      ch = s.charAt(pos - 1);
      if (ch in counts[pos]) {
        counts[pos][ch] += 1;
      }
    }
    return counts;
  }

  // costFunctions.frequencyRatios takes the set of characters that are
  //  common to the raw text and formatted text. At each position in the
  //  formatted text, for each character in the common set, it computes the
  //  square of the difference between the frequency ratios in the raw text
  //  and the formatted text at this position. The frequency ratio of a
  //  character at a position is the character's frequency to the left of
  //  the position divided by its overall frequency. The sum of the squared
  //  differences over the common characters is the score for a position.
  costFunctions.frequencyRatios = function (s, sCursor, t) {
    var chars = getCommonChars(s, t),
        sCounts = getLeftCounts(s, chars),
        // We have one set of raw counts because the raw cursor is fixed.
        sCountsHere = sCounts[sCursor],
        sTotals = sCounts[s.length],
        sRatios = new Array(chars.length),
        tCounts = getLeftCounts(t, chars),
        // The counts in the formatted text vary with the candidate cursor.
        tCountsHere,
        tTotals = tCounts[t.length],
        scores = new Array(t.length + 1),
        tCursor, i, ch, tRatio, cost;
    // Precompute the frequency ratios at the raw cursor position.
    for (i = 0; i < chars.length; ++i) {
      ch = chars[i];
      sRatios[ch] = sCountsHere[ch] / sTotals[ch];
    }
    for (tCursor = 0; tCursor <= t.length; ++tCursor) {
      // Look up the counts at the current position in the formatted text.
      tCountsHere = tCounts[tCursor];
      cost = 0;
      for (i = 0; i < chars.length; ++i) {
        ch = chars[i];
        tRatio = tCountsHere[ch] / tTotals[ch];
        cost += Math.pow(sRatios[ch] - tRatio, 2);
      }
      scores[tCursor] = cost;
    }
    return scores;
  };

  // retrospective.makeMaintainer returns a function that takes an instance
  //  of the cursor-maintenance problem and returns an object containing the
  //  cursor position computed by the retrospective approach. The resulting
  //  cursor maintainer does not check for equality of the formatted text
  //  and raw text. The caller should perform this check and only call the
  //  cursor maintainer if the formatted text differs from the raw text.
  // costFunction: A function that is given the raw text, raw cursor
  //  position, and formatted text. It computes an array of scores for
  //  every position in the formatted text. Lower scores indicate better
  //  cursor positions. This module has two built-in cost functions:
  //  costFunctions.splitLevenshtein and costFunctions.frequencyRatios.
  retrospective.makeMaintainer = function (costFunction) {
    if (costFunction === undefined) {
      costFunction = costFunctions.frequencyRatios;
    }
    return function (raw, cursor, formatted) {
      var cost, pos,
          scores = costFunction(raw, cursor, formatted),
          bestCost = scores[0],
          bestPos = 0;
      for (pos = 1; pos <= formatted.length; ++pos) {
        cost = scores[pos];
        if (cost < bestCost) {
          bestCost = cost;
          bestPos = pos;
        }
      }
      return { cursor: bestPos, scores: scores };
    };
  };

  // retrospective.augmentFormat returns a cursor-maintaining formatter
  //  based on a given format. A cursor-maintaining formatter takes raw text
  //  and a raw cursor position; it returns formatted text and a new cursor
  //  position.
  // format: A function that takes raw text and returns formatted text.
  // costFunction: the argument to retrospective.makeMaintainer.
  retrospective.augmentFormat = function (format, costFunction) {
    var maintainer;
    if (costFunction === undefined) {
      costFunction = costFunctions.frequencyRatios;
    }
    maintainer = retrospective.makeMaintainer(costFunction);
    return function (raw, cursor) {
      var formatted = format(raw),
          result = maintainer(raw, cursor, formatted);
      result.text = formatted;
      return result;
    };
  };

  return {
    retrospective: retrospective,
    layer: layer,
    TextWithCursor: TextWithCursor
  };
})();
<commit_msg>Add equality checks to the format augmenters<commit_after>var CursorMaintainer = (function () {
  'use strict';

  var layer,          // A statistical approach configured per format.
      retrospective,  // Format-independent statistical cursor maintenance.
      costFunctions;  // Cost functions for the retrospective approach.

  //--- Meta approach: entails reimplementing the format with
  //  elementary operations on a text-with-cursor object.

  function TextWithCursor(text, cursor) {
    this.text = text || '';
    this.cursor = cursor || 0;
  }

  TextWithCursor.prototype.length = function () {
    return this.text.length;
  };

  // TextCursor.read gets one character by default, several if specified.
  TextWithCursor.prototype.read = function (begin, length) {
    if (length === undefined) {
      length = 1;
    }
    if (length <= 0) {
      return;
    }
    return this.text.substring(begin, begin + length);
  };

  // TextWithCursor.insert adds text at the specified insertion point and
  //  shifts the cursor if needed. The cursor is unchanged if it is at or to
  //  the left of the insertion point.
  TextWithCursor.prototype.insert = function (begin, subtext) {
    this.text = this.text.substring(0, begin) + subtext +
        this.text.substring(begin);
    if (this.cursor > begin) {
      this.cursor += subtext.length;
    }
  };

  // TextWithCursor.delete removes one character by default, several if
  //  specified, then shifts the cursor if needed. The cursor is unchanged
  //  if it is at or to the left of the deleted characters.
  TextWithCursor.prototype.delete = function (begin, length) {
    if (length === undefined) {
      length = 1;
    }
    if (length <= 0) {
      return;
    }
    this.text = this.text.substring(0, begin) +
        this.text.substring(begin + length);
    if (this.cursor > begin) {
      this.cursor -= Math.min(this.cursor - begin, length);
    }
  };

  //--- Layer approach: a statistical approach that looks at layers of text
  //  induced by character sets specified for a format. Within each layer of
  //  the formatted text, we seek the cursor position where the proportion
  //  of ratio characters to the left is closest to the equivalent proportion
  //  at the raw cursor position in the raw text. If several cursor positions
  //  are equally close, we move to the next layer. If a tie-breaker is
  //  needed, we take either the left or right end of the final range,
  //  as configured for the format.

  layer = {};

  // layer.makeMaintainer returns a function that takes an instance of the
  //  cursor-maintenance problem and returns an object containing the
  //  cursor position computed by the layer approach. The resulting cursor
  //  maintainer does not check for equality of the formatted text and
  //  raw text. The caller should perform this check and only call the
  //  cursor maintainer if the formatted text differs from the raw text.
  // testers: An array of objects, each with a test() method that takes a
  //  character and returns a Boolean value. A regex is such an object.
  // preferRight: A Boolean value. If this argument is omitted, ties are
  //  broken to the left, i.e., preferRight is false by default.
  layer.makeMaintainer = function (testers, preferRight) {
    return function (raw, cursor, formatted) {
      var rawCount, rawTotal, rawRatio,
          formattedCounts, formattedTotal, formattedRatio,
          delta, bestDelta, bestFormattedRatio,
          rank, tester, pos,
          left = 0,
          right = formatted.length,
          bestLeft = left, bestRight = right;
      if (right == 0) {
        return { text: '', cursor: 0 };
      }
      for (rank = 0; rank < testers.length; ++rank) {
        tester = testers[rank];
        // Scan the raw text, counting current layer characters.
        rawCount = 0;
        rawTotal = 0;
        for (pos = 0; pos < raw.length; ++pos) {
          if (tester.test(raw.charAt(pos))) {
            ++rawTotal;
            // We only need the ratio for the raw cursor position.
            if (pos < cursor) {
              ++rawCount;
            }
          }
        }
        // Bail out if the raw text has no layer characters.
        if (rawTotal == 0) {
          continue;
        }
        // Compute the raw cursor's ratio in the current layer.
        rawRatio = rawCount / rawTotal;
        // Scan the formatted text and store the layer count at each position.
        formattedCounts = new Array(formatted.length + 1);
        formattedCounts[0] = 0;
        formattedTotal = 0;
        for (pos = 0; pos < formatted.length; ++pos) {
          if (tester.test(formatted.charAt(pos))) {
            ++formattedTotal;
          }
          formattedCounts[pos + 1] = formattedTotal;
        }
        // Bail out if the formatted text has no layer characters.
        if (formattedTotal == 0) {
          continue;
        }
        // Scan the layer counts to compute the ratio at each cursor position.
        // Keep track of the closest ratio and the indices where it occurs.
        bestFormattedRatio = formattedCounts[left] / formattedTotal;
        bestDelta = Math.abs(rawRatio - bestFormattedRatio);
        bestLeft = bestRight = left;
        for (pos = left + 1; pos <= right; ++pos) {
          formattedRatio = formattedCounts[pos] / formattedTotal;
          delta = Math.abs(rawRatio - formattedRatio);
          if (delta == bestDelta) {
            bestRight = pos;
          } else if (delta < bestDelta) {
            bestFormattedRatio = formattedRatio;
            bestDelta = delta;
            bestLeft = bestRight = pos;
          }
        }
        if (bestLeft == bestRight) {
          break;
        }
        // If there is a tie, shrink the range for the next layer.
        left = bestLeft;
        right = bestRight;
      }
      if (preferRight) {
        return { cursor: bestRight };
      }
      return { cursor: bestLeft };
    };
  };

  // layer.augmentFormat returns a cursor-maintaining formatter based on a
  //  given format. A cursor-maintaining formatter takes raw text and a raw
  //  cursor position; it returns formatted text and a new cursor position.
  // format: A function that takes raw text and returns formatted text.
  //  testers, preferRight: arguments to layer.makeMaintainer.
  layer.augmentFormat = function (format, testers, preferRight) {
    var maintainer = layer.makeMaintainer(testers, preferRight);
    return function (raw, cursor) {
      var result,
          formatted = format(raw);
      // Check for equality to avoid erroneous cursor positions in the trivial
      //  case where formatted == raw.
      if (formatted == raw) {
        return { text: raw, cursor: cursor };
      }
      result = maintainer(raw, cursor, formatted);
      result.text = formatted;
      return result;
    };
  };

  //--- Retrospective approach: A statistical approach that is not configured
  //  for any particular format. Instead, we configure it with a cost function
  //  that computes a score for every position in the formatted text. The
  //  position with the lowest score is chosen as the new cursor position.
  //  If several positions are in a tie, the leftmost is chosen.

  costFunctions = {};
  retrospective = { costFunctions: costFunctions };

  // levenshtein implements the well-known Levenshtein distance. It is
  //  called by the costFunctions.splitLevenshtein.
  function levenshtein(s, t) {
    var n = s.length,
        m = t.length,
        current,
        previous,
        temp,
        i, j;
    if (Math.min(n, m) == 0) {
      return Math.max(n, m);
    }
    current = new Array(m + 1);
    for (i = 0; i <= m; ++i) {
      current[i] = i;
    }
    previous = new Array(m + 1);
    for (i = 1; i <= n; ++i) {
      temp = current;
      current = previous;
      previous = temp;
      current[0] = previous[0] + 1;
      for (j = 1; j <= m; ++j) {
        if (t.charAt(j - 1) == s.charAt(i - 1)) {
          current[j] = previous[j - 1];
        } else {
          current[j] = Math.min(previous[j - 1] + 1,
                                previous[j] + 1,
                                current[j - 1] + 1);
        }
      }
    }
    return current[m];
  }

  // costFunctions.splitLevenshtein splits the raw text and formatted text
  //  at respective cursor positions, computes the Levenshtein distance
  //  between the left parts, then between the right parts, and takes the
  //  sum. The raw cursor position is passed to this function. It is compared
  //  to all positions in the formatted text, and the resulting scores
  //  are returned in an array.
  costFunctions.splitLevenshtein = function (s, sCursor, t) {
    var tCursor,
        left, right,
        scores = new Array(t.length + 1);
    for (tCursor = 0; tCursor <= t.length; ++tCursor) {
      left = levenshtein(s.substring(0, sCursor), t.substring(0, tCursor));
      right = levenshtein(s.substring(sCursor), t.substring(tCursor));
      scores[tCursor] = left + right;
    }
    return scores;
  };

  // getCommonChars finds the set of characters that appear in both of the
  //  given strings. It is called by costFunctions.frequencyRatios.
  function getCommonChars(s, t) {
    var sCharSet = {},
        tCharSet = {},
        chars = [],
        i;
    for (i = 0; i < s.length; ++i) {
      sCharSet[s.charAt(i)] = true;
    }
    for (i = 0; i < t.length; ++i) {
      tCharSet[t.charAt(i)] = true;
    }
    Object.keys(sCharSet).forEach(function (ch) {
      if (ch in tCharSet) {
        chars.push(ch);
      }
    });
    return chars;
  }

  // getLeftCounts is given an arbitrary string and an array containing a
  //  set of characters. It returns an array containing, for each position
  //  in the string, an object that maps each character in the set to the
  //  number of times the character appears to the left of the position.
  function getLeftCounts(s, chars) {
    var counts = new Array(s.length + 1),
        pos, i, ch;
    // Initialize all character frequencies to zero.
    counts[0] = {};
    for (i = 0; i < chars.length; ++i) {
      counts[0][chars[i]] = 0;
    }
    // Iterate over string positions, dealing with set characters in parallel.
    for (pos = 1; pos <= s.length; ++pos) {
      counts[pos] = {};
      // Copy the previous character frequencies.
      for (i = 0; i < chars.length; ++i) {
        counts[pos][chars[i]] = counts[pos - 1][chars[i]];
      }
      // Update the frequency of one character at the most.
      ch = s.charAt(pos - 1);
      if (ch in counts[pos]) {
        counts[pos][ch] += 1;
      }
    }
    return counts;
  }

  // costFunctions.frequencyRatios takes the set of characters that are
  //  common to the raw text and formatted text. At each position in the
  //  formatted text, for each character in the common set, it computes the
  //  square of the difference between the frequency ratios in the raw text
  //  and the formatted text at this position. The frequency ratio of a
  //  character at a position is the character's frequency to the left of
  //  the position divided by its overall frequency. The sum of the squared
  //  differences over the common characters is the score for a position.
  costFunctions.frequencyRatios = function (s, sCursor, t) {
    var chars = getCommonChars(s, t),
        sCounts = getLeftCounts(s, chars),
        // We have one set of raw counts because the raw cursor is fixed.
        sCountsHere = sCounts[sCursor],
        sTotals = sCounts[s.length],
        sRatios = new Array(chars.length),
        tCounts = getLeftCounts(t, chars),
        // The counts in the formatted text vary with the candidate cursor.
        tCountsHere,
        tTotals = tCounts[t.length],
        scores = new Array(t.length + 1),
        tCursor, i, ch, tRatio, cost;
    // Precompute the frequency ratios at the raw cursor position.
    for (i = 0; i < chars.length; ++i) {
      ch = chars[i];
      sRatios[ch] = sCountsHere[ch] / sTotals[ch];
    }
    for (tCursor = 0; tCursor <= t.length; ++tCursor) {
      // Look up the counts at the current position in the formatted text.
      tCountsHere = tCounts[tCursor];
      cost = 0;
      for (i = 0; i < chars.length; ++i) {
        ch = chars[i];
        tRatio = tCountsHere[ch] / tTotals[ch];
        cost += Math.pow(sRatios[ch] - tRatio, 2);
      }
      scores[tCursor] = cost;
    }
    return scores;
  };

  // retrospective.makeMaintainer returns a function that takes an instance
  //  of the cursor-maintenance problem and returns an object containing the
  //  cursor position computed by the retrospective approach. The resulting
  //  cursor maintainer does not check for equality of the formatted text
  //  and raw text. The caller should perform this check and only call the
  //  cursor maintainer if the formatted text differs from the raw text.
  // costFunction: A function that is given the raw text, raw cursor
  //  position, and formatted text. It computes an array of scores for
  //  every position in the formatted text. Lower scores indicate better
  //  cursor positions. This module has two built-in cost functions:
  //  costFunctions.splitLevenshtein and costFunctions.frequencyRatios.
  retrospective.makeMaintainer = function (costFunction) {
    if (costFunction === undefined) {
      costFunction = costFunctions.frequencyRatios;
    }
    return function (raw, cursor, formatted) {
      var cost, pos,
          scores = costFunction(raw, cursor, formatted),
          bestCost = scores[0],
          bestPos = 0;
      for (pos = 1; pos <= formatted.length; ++pos) {
        cost = scores[pos];
        if (cost < bestCost) {
          bestCost = cost;
          bestPos = pos;
        }
      }
      return { cursor: bestPos, scores: scores };
    };
  };

  // retrospective.augmentFormat returns a cursor-maintaining formatter
  //  based on a given format. A cursor-maintaining formatter takes raw text
  //  and a raw cursor position; it returns formatted text and a new cursor
  //  position.
  // format: A function that takes raw text and returns formatted text.
  // costFunction: the argument to retrospective.makeMaintainer.
  retrospective.augmentFormat = function (format, costFunction) {
    var maintainer;
    if (costFunction === undefined) {
      costFunction = costFunctions.frequencyRatios;
    }
    maintainer = retrospective.makeMaintainer(costFunction);
    return function (raw, cursor) {
      var result,
          formatted = format(raw);
      // Check for equality to avoid erroneous cursor positions in the trivial
      //  case where formatted == raw. We add a zero-length score array to the
      //  result because functions returned by retrospective.makeMaintainer
      //  include scores in the return value.
      if (formatted == raw) {
        return { text: raw, cursor: cursor, scores: [] };
      }
      result = maintainer(raw, cursor, formatted);
      result.text = formatted;
      return result;
    };
  };

  return {
    retrospective: retrospective,
    layer: layer,
    TextWithCursor: TextWithCursor
  };
})();
<|endoftext|>


<commit_before>function Nests(root) {
  this.root = root
}

module.exports = Nests
<commit_msg>- adding doesExist property to Nest prototype - adding setExistance method to Nest prototype<commit_after>function Nest(root) {
  this.root = root
  this.doesExist = null
}

Nest.prototype.setExistance = function(bool){
  this.doesExist = bool
}

module.exports = Nest
<|endoftext|>


<commit_before>var express = require('express');
var bodyParser = require('body-parser');
var UserInvite = require('../models/userInvite');
var Verify = require('./verify');

var router = express.Router();
router.use(bodyParser.json());

//#################################################################################################
//#################################################################################################
router.route('/')

//get all user invites:
.get(function(req, res) {
    UserInvite.find({})
        .populate('role')
        .exec(function(err, userInvites) {
            if(err) throw err;
            res.json(userInvites);
    });
})

//add a new user invite to the system:
.post(Verify.verifyOrdinaryUser, function(req, res, next) {
    UserInvite.create(req.body, function(err, userInvite) {
        if(err) return next(err);
        console.log("New user invite created");
        res.json(userInvite);
    });
})

.delete(Verify.verifyOrdinaryUser, function(req, res, next) {
    UserInvite.remove({}, function(err, userInvites) {
        if(err) return next(err);
        console.log("Removed: \n" + userInvites);
        res.json("All user invites removed.");
    });
});

//#################################################################################################
//#################################################################################################
router.route('/:userInviteId')

///GET user invite by ID
.get(Verify.verifyOrdinaryUser, function(req, res) {
    UserInvite.findById(req.params.userInviteId)
        .populate('role')
        .exec(function(err, userInvite) {
            if(err) throw err;
            res.json(userInvite);
    });
})

//PUT update user invite by ID
.put(Verify.verifyOrdinaryUser, function(req, res) {
    UserInvite.findByIdAndUpdate(req.params.userInviteId, {$set: req.body}, {new: true})
        .exec(function(err, userInvite) {
            if(err) throw err;
            res.json(userInvite);
    });
})

///DELETE user invite by ID
.delete(Verify.verifyOrdinaryUser, function(req, res) {
    UserInvite.findById(req.params.userInviteId)
        .exec(function(err, userInvite) {
            if(err) throw err;
            userInvite.remove();
            res.json("Successfully removed user invite " + userInvite.invite_key);
    });
});

//#################################################################################################
//#################################################################################################
router.route('/findByKey/:inviteKey')

///GET user invite by ID
.get(Verify.verifyOrdinaryUser, function(req, res) {
    UserInvite.find({invite_key: req.params.inviteKey})
        .populate('role')
        .exec(function(err, userInvite) {
            if(err) throw err;
            res.json(userInvite);
    });
});

module.exports = router;<commit_msg>minor change to userInvites, added req.query params to GET<commit_after>var express = require('express');
var bodyParser = require('body-parser');
var UserInvite = require('../models/userInvite');
var Verify = require('./verify');

var router = express.Router();
router.use(bodyParser.json());

//#################################################################################################
//#################################################################################################
router.route('/')

//get all user invites:
.get(function(req, res) {
    UserInvite.find(req.query)
        .populate('role')
        .exec(function(err, userInvites) {
            if(err) throw err;
            res.json(userInvites);
    });
})

//add a new user invite to the system:
.post(Verify.verifyOrdinaryUser, function(req, res, next) {
    UserInvite.create(req.body, function(err, userInvite) {
        if(err) return next(err);
        console.log("New user invite created");
        res.json(userInvite);
    });
})

.delete(Verify.verifyOrdinaryUser, function(req, res, next) {
    UserInvite.remove({}, function(err, userInvites) {
        if(err) return next(err);
        console.log("Removed: \n" + userInvites);
        res.json("All user invites removed.");
    });
});

//#################################################################################################
//#################################################################################################
router.route('/:userInviteId')

///GET user invite by ID
.get(Verify.verifyOrdinaryUser, function(req, res) {
    UserInvite.findById(req.params.userInviteId)
        .populate('role')
        .exec(function(err, userInvite) {
            if(err) throw err;
            res.json(userInvite);
    });
})

//PUT update user invite by ID
.put(Verify.verifyOrdinaryUser, function(req, res) {
    UserInvite.findByIdAndUpdate(req.params.userInviteId, {$set: req.body}, {new: true})
        .exec(function(err, userInvite) {
            if(err) throw err;
            res.json(userInvite);
    });
})

///DELETE user invite by ID
.delete(Verify.verifyOrdinaryUser, function(req, res) {
    UserInvite.findById(req.params.userInviteId)
        .exec(function(err, userInvite) {
            if(err) throw err;
            userInvite.remove();
            res.json("Successfully removed user invite " + userInvite.invite_key);
    });
});

//#################################################################################################
//#################################################################################################
router.route('/findByKey/:inviteKey')

///GET user invite by ID
.get(Verify.verifyOrdinaryUser, function(req, res) {
    UserInvite.find({invite_key: req.params.inviteKey})
        .populate('role')
        .exec(function(err, userInvite) {
            if(err) throw err;
            res.json(userInvite);
    });
});

module.exports = router;<|endoftext|>


<commit_before>(function webpackUniversalModuleDefinition(root, factory) {
	if(typeof exports === 'object' && typeof module === 'object')
		module.exports = factory();
	else if(typeof define === 'function' && define.amd)
		define([], factory);
	else if(typeof exports === 'object')
		exports["VueRecaptcha"] = factory();
	else
		root["VueRecaptcha"] = factory();
})(this, function() {
return /******/ (function(modules) { // webpackBootstrap
/******/ 	// The module cache
/******/ 	var installedModules = {};

/******/ 	// The require function
/******/ 	function __webpack_require__(moduleId) {

/******/ 		// Check if module is in cache
/******/ 		if(installedModules[moduleId])
/******/ 			return installedModules[moduleId].exports;

/******/ 		// Create a new module (and put it into the cache)
/******/ 		var module = installedModules[moduleId] = {
/******/ 			exports: {},
/******/ 			id: moduleId,
/******/ 			loaded: false
/******/ 		};

/******/ 		// Execute the module function
/******/ 		modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);

/******/ 		// Flag the module as loaded
/******/ 		module.loaded = true;

/******/ 		// Return the exports of the module
/******/ 		return module.exports;
/******/ 	}

/******/ 	// expose the modules object (__webpack_modules__)
/******/ 	__webpack_require__.m = modules;

/******/ 	// expose the module cache
/******/ 	__webpack_require__.c = installedModules;

/******/ 	// __webpack_public_path__
/******/ 	__webpack_require__.p = "";

/******/ 	// Load entry module and return exports
/******/ 	return __webpack_require__(0);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ function(module, exports, __webpack_require__) {

	'use strict';

	var _recaptcha = __webpack_require__(1);

	var _recaptcha2 = _interopRequireDefault(_recaptcha);

	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

	module.exports = _recaptcha2.default;

/***/ },
/* 1 */
/***/ function(module, exports, __webpack_require__) {

	var __vue_script__, __vue_template__
	__vue_script__ = __webpack_require__(2)
	if (__vue_script__ &&
	    __vue_script__.__esModule &&
	    Object.keys(__vue_script__).length > 1) {
	  console.warn("[vue-loader] src/recaptcha.vue: named exports in *.vue files are ignored.")}
	__vue_template__ = __webpack_require__(4)
	module.exports = __vue_script__ || {}
	if (module.exports.__esModule) module.exports = module.exports.default
	if (__vue_template__) {
	(typeof module.exports === "function" ? (module.exports.options || (module.exports.options = {})) : module.exports).template = __vue_template__
	}

/***/ },
/* 2 */
/***/ function(module, exports, __webpack_require__) {

	'use strict';

	Object.defineProperty(exports, "__esModule", {
	  value: true
	});

	var _recaptcha = __webpack_require__(3);

	var recaptcha = _interopRequireWildcard(_recaptcha);

	function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } }

	var widgetId = null; // <template>
	//

	// </template>
	//
	//

	module.exports = exports['default'];

/***/ },
/* 3 */
/***/ function(module, exports) {

	'use strict';

	Object.defineProperty(exports, "__esModule", {
	  value: true
	});
	exports.getRecaptcha = getRecaptcha;
	exports.checkRecaptchaLoad = checkRecaptchaLoad;
	exports.assertRecaptchaLoad = assertRecaptchaLoad;
	exports.render = render;
	exports.reset = reset;
	var promise = {};
	var recaptcha = null;

	promise.promise = new Promise(function (resolve) {
	  promise.resolve = resolve;
	});

	window.vueRecaptchaApiLoaded = function () {
	  recaptcha = window.grecaptcha;
	  promise.resolve(recaptcha);
	};

	function getRecaptcha() {
	  if (recaptcha !== null) {
	    return Promise.resolve(recaptcha);
	  }

	  return promise.promise;
	}

	function checkRecaptchaLoad() {
	  if (window.hasOwnProperty('grecaptcha')) {
	    window.vueRecaptchaApiLoaded();
	  }
	}

	function assertRecaptchaLoad() {
	  if (recaptcha === null) {
	    throw new Error('ReCAPTCHA has not been loaded');
	  }
	}

	function render(ele, key, options) {
	  return getRecaptcha().then(function (recaptcha) {
	    var opts = Object.assign({}, { sitekey: key }, options);
	    return recaptcha.render(ele, opts);
	  });
	}

	function reset(widgetId) {
	  if (typeof widgetId === 'undefined') {
	    return false;
	  }
	  assertRecaptchaLoad();
	  getRecaptcha().then(function (recaptcha) {
	    recaptcha.reset(widgetId);
	  });
	}

/***/ },
/* 4 */
/***/ function(module, exports) {

	module.exports = "\n

\n";

/***/ }
/******/ ])
});
;<commit_msg>Compile<commit_after>(function webpackUniversalModuleDefinition(root, factory) {
	if(typeof exports === 'object' && typeof module === 'object')
		module.exports = factory();
	else if(typeof define === 'function' && define.amd)
		define([], factory);
	else if(typeof exports === 'object')
		exports["VueRecaptcha"] = factory();
	else
		root["VueRecaptcha"] = factory();
})(this, function() {
return /******/ (function(modules) { // webpackBootstrap
/******/ 	// The module cache
/******/ 	var installedModules = {};

/******/ 	// The require function
/******/ 	function __webpack_require__(moduleId) {

/******/ 		// Check if module is in cache
/******/ 		if(installedModules[moduleId])
/******/ 			return installedModules[moduleId].exports;

/******/ 		// Create a new module (and put it into the cache)
/******/ 		var module = installedModules[moduleId] = {
/******/ 			exports: {},
/******/ 			id: moduleId,
/******/ 			loaded: false
/******/ 		};

/******/ 		// Execute the module function
/******/ 		modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);

/******/ 		// Flag the module as loaded
/******/ 		module.loaded = true;

/******/ 		// Return the exports of the module
/******/ 		return module.exports;
/******/ 	}

/******/ 	// expose the modules object (__webpack_modules__)
/******/ 	__webpack_require__.m = modules;

/******/ 	// expose the module cache
/******/ 	__webpack_require__.c = installedModules;

/******/ 	// __webpack_public_path__
/******/ 	__webpack_require__.p = "";

/******/ 	// Load entry module and return exports
/******/ 	return __webpack_require__(0);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ function(module, exports, __webpack_require__) {

	'use strict';

	var _recaptcha = __webpack_require__(1);

	var _recaptcha2 = _interopRequireDefault(_recaptcha);

	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

	module.exports = _recaptcha2.default;

/***/ },
/* 1 */
/***/ function(module, exports, __webpack_require__) {

	var __vue_exports__, __vue_options__

	/* script */
	__vue_exports__ = __webpack_require__(2)

	/* template */
	var __vue_template__ = __webpack_require__(4)
	__vue_options__ = __vue_exports__ = __vue_exports__ || {}
	if (
	  typeof __vue_exports__.default === "object" ||
	  typeof __vue_exports__.default === "function"
	) {
	__vue_options__ = __vue_exports__ = __vue_exports__.default
	}
	if (typeof __vue_options__ === "function") {
	  __vue_options__ = __vue_options__.options
	}
	__vue_options__.render = __vue_template__.render
	__vue_options__.staticRenderFns = __vue_template__.staticRenderFns

	module.exports = __vue_exports__

/***/ },
/* 2 */
/***/ function(module, exports, __webpack_require__) {

	'use strict';

	Object.defineProperty(exports, "__esModule", {
	  value: true
	});

	var _recaptcha = __webpack_require__(3);

	var recaptcha = _interopRequireWildcard(_recaptcha);

	function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } }

	var widgetId = null; //
	//
	//
	//

	exports.default = {
	  props: {
	    key: {
	      type: String,
	      required: true
	    },
	    options: {
	      type: Object,
	      default: function _default() {
	        return {};
	      }
	    }
	  },
	  created: function created() {
	    recaptcha.checkRecaptchaLoad();
	  },
	  mounted: function mounted() {
	    var self = this;
	    var opts = Object.assign({}, this.options, {
	      callback: this.emitVerify,
	      'expired-callback': this.emitExpired
	    });
	    recaptcha.render(this.$refs.container, this.key, opts).then(function (id) {
	      widgetId = id;
	      self.$emit('render', widgetId);
	    });
	  },

	  methods: {
	    reset: function reset() {
	      recaptcha.reset(widgetId);
	    },
	    emitVerify: function emitVerify(response) {
	      this.$emit('verify', response);
	    },
	    emitExpired: function emitExpired() {
	      this.$emit('expired');
	    }
	  },
	  events: {
	    recaptchaReset: function recaptchaReset() {
	      this.reset();
	    }
	  }
	};
	module.exports = exports['default'];

/***/ },
/* 3 */
/***/ function(module, exports) {

	'use strict';

	Object.defineProperty(exports, "__esModule", {
	  value: true
	});
	exports.getRecaptcha = getRecaptcha;
	exports.checkRecaptchaLoad = checkRecaptchaLoad;
	exports.assertRecaptchaLoad = assertRecaptchaLoad;
	exports.render = render;
	exports.reset = reset;
	var promise = {};
	var recaptcha = null;

	promise.promise = new Promise(function (resolve) {
	  promise.resolve = resolve;
	});

	window.vueRecaptchaApiLoaded = function () {
	  recaptcha = window.grecaptcha;
	  promise.resolve(recaptcha);
	};

	function getRecaptcha() {
	  if (recaptcha !== null) {
	    return Promise.resolve(recaptcha);
	  }

	  return promise.promise;
	}

	function checkRecaptchaLoad() {
	  if (window.hasOwnProperty('grecaptcha')) {
	    window.vueRecaptchaApiLoaded();
	  }
	}

	function assertRecaptchaLoad() {
	  if (recaptcha === null) {
	    throw new Error('ReCAPTCHA has not been loaded');
	  }
	}

	function render(ele, key, options) {
	  return getRecaptcha().then(function (recaptcha) {
	    var opts = Object.assign({}, { sitekey: key }, options);
	    return recaptcha.render(ele, opts);
	  });
	}

	function reset(widgetId) {
	  if (typeof widgetId === 'undefined') {
	    return false;
	  }
	  assertRecaptchaLoad();
	  getRecaptcha().then(function (recaptcha) {
	    recaptcha.reset(widgetId);
	  });
	}

/***/ },
/* 4 */
/***/ function(module, exports) {

	module.exports={render:function (){with(this) {
	  return _h('div', {
	    ref: "container"
	  })
	}},staticRenderFns: []}

/***/ }
/******/ ])
});
;<|endoftext|>