<commit_before>/**
Handles all actual API calls to salesforce.
Implements caching of describe data to prevent excessive API calls continually retrieving it.
**/
"use strict";

var log = new Logger();

Object.create = function(o){
    var result = function(){};
    result.prototype = o;
    return new result();
}

var OrganizationSchema = function(){
    if(!(this instanceof OrganizationSchema))
        throw Error("Constructor called as a function.");
    this.sObjectTypes = {};
}

var SObjectType = function(sforceXML, connectionId){
    if(sforceXML == null || sforceXML == undefined)
        throw Error("First argument cannot be null");
    if(!(this instanceof SObjectType))
        throw Error("Constructor called as a function.");
    this.name = sforceXML.name;
    this.label = sforceXML.label;
    this.labelPlural = sforceXML.label;
    this.activateable = sforceXML.activateable;
    this.createable = sforceXML.createable;
    this.custom = sforceXML.custom;
    this.customSetting = sforceXML.custom;
    this.deletable = sforceXML.deletable;
    this.deprecatedAndHidden = sforceXML.deprecatedAndHidden;
    this.feedEnabled = sforceXML.feedEnabled;
    this.keyPrefix =  sforceXML.keyPrefix;
    this.layoutable = sforceXML.layoutable;
    this.mergeable = sforceXML.mergeable;
    this.queryable = sforceXML.queryable;
    this.replicateable = sforceXML.replicateable;
    this.retrieveable = sforceXML.retrieveable;
    this.searchable = sforceXML.searchable;
    this.triggerable = sforceXML.triggerable;
    this.undeletable = sforceXML.undeletable;
    this.updateable = sforceXML.updateable;

    var that = this;
    var fields = null;
    var connectionId = connectionId;
    var fetchFields = function(){
        var res = cache.cachedConnections[connectionId].getFieldsForSObject(that.name);
        fields = [];
        for(var i = 0; i < res.fields.length; i++)
            fields.push( new SObjectField(res.fields[i]) );
        console.log('Got fields: ',res,' for ',that.name);
    }

    this.getFields = function(){
        fetchFields();
        var result = [];
        for(var fieldName in fields){
            result.push(fields[fieldName]);
        }
        return result;
    };
    this.getField = function(fieldName){
        fetchFields();
        var result = fields[fieldName.toLowerCase()];
        return result;
    };
}

var SObjectField = function(sforceXML){
    if(!(this instanceof SObjectField))
        throw Error("Constructor called as a function.");
    this.autoNumber = sforceXML.autoNumber;
    this.byteLength = sforceXML.byteLength;
    this.calculated = sforceXML.calculated;
    this.calculated = sforceXML.calculated;
    this.caseSensitive = sforceXML.caseSensitive;
    this.createable = sforceXML.createable;
    this.custom = sforceXML.custom;
    this.defaultedOnCreate = sforceXML.defaultedOnCreate;
    this.deprecatedAndHidden = sforceXML.deprecatedAndHidden;
    this.digits = sforceXML.digits;
    this.filterable = sforceXML.filterable;
    this.groupable = sforceXML.groupable;
    this.idLookup = sforceXML.idLookup;
    this.label = sforceXML.label;
    this.length = sforceXML.length;
    this.name = sforceXML.name;
    this.nameField = sforceXML.nameField;
    this.namePointing = sforceXML.namePointing;
    this.nillable = sforceXML.nillable;
    this.permissionable = sforceXML.permissionable;
    this.precision = sforceXML.precision;
    this.restrictedPicklist = sforceXML.restrictedPicklist;
    this.scale = sforceXML.scale;
    this.soapType = sforceXML.soapType;
    this.sortable = sforceXML.sortable;
    this.type = sforceXML.type;
    this.unique = sforceXML.unique;
    this.updateable = sforceXML.updateable;
}

//TODO: switch to a constructor
var Connection = {
    connectionId: null, //aka sid_client
    sfconnection: null,
    globalDescribe: null,
	fullDescribes: new Object(),
    lastUsed: Date.now(),
    fetchGlobalDescribe: function(){
        this.globalDescribe = this.sfconnection.describeGlobal();
    },
    getOrganizationSchema: function(){
        //TODO: add better caching (on connection instantiation?)
        if(this.globalDescribe == null)
            this.fetchGlobalDescribe();
        var result = new OrganizationSchema();
        for(var i = 0; i < this.globalDescribe.sobjects.length; i++){
            var describe = this.globalDescribe.sobjects[i];
            var objType = new SObjectType(describe,this.connectionId);
            result.sObjectTypes[objType.name.toLowerCase()] = objType;
        }
        return result;
    },
    getDescribeForId: function(recordId){
        if(this.globalDescribe == null)
            this.fetchGlobalDescribe();
        var result;
        for(var i = 0; i < this.globalDescribe.sobjects.length; i++){
            var describe = this.globalDescribe.sobjects[i];
            if(describe.keyPrefix == recordId.substring(0,3))
                result = describe;
        }
        return result;
    },
	getFieldsForSObject: function(typeName){
		var describe;
		console.log("looking up full describe for "+typeName);
        if(this.fullDescribes[typeName] !== undefined){
			describe = this.fullDescribes[typeName];
		}else {
			describe = this.sfconnection.describeSObject(typeName);
			console.log("Got describe: ",describe);
			this.fullDescribes[typeName] = describe;
		}
		return describe;
	}

}

var cache = {
    cachedConnections: {},
    getConnection: function(context){
        if(context == null || context.sessionId == null || context.sid_Client == null)
            throw "Invalid context";
        var result = this.cachedConnections[context.sid_Client];
        if(result == null){
            result = Object.create(Connection);
            result.connectionId = context.sid_Client;
            this.cachedConnections[result.connectionId] = result;
            result.sfconnection = new sforce.Connection();
            console.log("Setting up API connection with session Id "+context.masterSessionId+" to "+context.sfhost);
            result.sfconnection.init(context.masterSessionId,"https://"+context.sfhost+"/services/Soap/u/25.0");
            UserContext.getUrl = function(path){ return "https://"+context.sfhost+path; }; //hack for apex.js to work
        }else{
            UserContext.getUrl = function(path){ return "https://"+context.sfhost+path; }; //hack for apex.js to work
        }
        result.lastUsed = Date.now();
        return result;
    },
    removeConnection: function(context){
        if(context == null || context.sessionId == null || context.sid_Client == null)
            throw "Invalid context";
        delete this.cachedConnections[context.sid_Client];
    }

};

var cleanupTimer = 900000; // 15 minutes - value is miliseconds between cleanup runs.
setInterval(
    function(){
        //Clear out connections unused for x seconds
        var maxConnectionCacheAge = config.getConfig("connection_cleanup_age") * 60 * 60 * 1000; //config is hrs - need it in ms
        console.log('Running old connection cleanup. Removing entries older than '+maxConnectionCacheAge+' miliseconds.');
        var now = Date.now();
        for(var key in cache.cachedConnections){
            if( (now - maxConnectionCacheAge) >= cache.cachedConnections[key].lastUsed){
                console.log('removing connection '+key+'. Last used '+cache.cachedConnections[key].lastUsed);
                delete cache.cachedConnections[key];
            }
        }
    },
    cleanupTimer
);

<commit_msg>Added default error handler to background page<commit_after>/**
Handles all actual API calls to salesforce.
Implements caching of describe data to prevent excessive API calls continually retrieving it.
**/
"use strict";

var log = new Logger();
window.onerror = function(message, source, row, col, err){
  log.addMessage("ERROR", {
      message: message,
      file: source,
      row: row,
      column: col,
      errorObject: err
  });
};

Object.create = function(o){
    var result = function(){};
    result.prototype = o;
    return new result();
};

var OrganizationSchema = function(){
    if(!(this instanceof OrganizationSchema))
        throw Error("Constructor called as a function.");
    this.sObjectTypes = {};
};

var SObjectType = function(sforceXML, connectionId){
    if(sforceXML == null || sforceXML == undefined)
        throw Error("First argument cannot be null");
    if(!(this instanceof SObjectType))
        throw Error("Constructor called as a function.");
    this.name = sforceXML.name;
    this.label = sforceXML.label;
    this.labelPlural = sforceXML.label;
    this.activateable = sforceXML.activateable;
    this.createable = sforceXML.createable;
    this.custom = sforceXML.custom;
    this.customSetting = sforceXML.custom;
    this.deletable = sforceXML.deletable;
    this.deprecatedAndHidden = sforceXML.deprecatedAndHidden;
    this.feedEnabled = sforceXML.feedEnabled;
    this.keyPrefix =  sforceXML.keyPrefix;
    this.layoutable = sforceXML.layoutable;
    this.mergeable = sforceXML.mergeable;
    this.queryable = sforceXML.queryable;
    this.replicateable = sforceXML.replicateable;
    this.retrieveable = sforceXML.retrieveable;
    this.searchable = sforceXML.searchable;
    this.triggerable = sforceXML.triggerable;
    this.undeletable = sforceXML.undeletable;
    this.updateable = sforceXML.updateable;

    var that = this;
    var fields = null;
    var connectionId = connectionId;
    var fetchFields = function(){
        var res = cache.cachedConnections[connectionId].getFieldsForSObject(that.name);
        fields = [];
        for(var i = 0; i < res.fields.length; i++)
            fields.push( new SObjectField(res.fields[i]) );
        console.log('Got fields: ',res,' for ',that.name);
    }

    this.getFields = function(){
        fetchFields();
        var result = [];
        for(var fieldName in fields){
            result.push(fields[fieldName]);
        }
        return result;
    };
    this.getField = function(fieldName){
        fetchFields();
        var result = fields[fieldName.toLowerCase()];
        return result;
    };
}

var SObjectField = function(sforceXML){
    if(!(this instanceof SObjectField))
        throw Error("Constructor called as a function.");
    this.autoNumber = sforceXML.autoNumber;
    this.byteLength = sforceXML.byteLength;
    this.calculated = sforceXML.calculated;
    this.calculated = sforceXML.calculated;
    this.caseSensitive = sforceXML.caseSensitive;
    this.createable = sforceXML.createable;
    this.custom = sforceXML.custom;
    this.defaultedOnCreate = sforceXML.defaultedOnCreate;
    this.deprecatedAndHidden = sforceXML.deprecatedAndHidden;
    this.digits = sforceXML.digits;
    this.filterable = sforceXML.filterable;
    this.groupable = sforceXML.groupable;
    this.idLookup = sforceXML.idLookup;
    this.label = sforceXML.label;
    this.length = sforceXML.length;
    this.name = sforceXML.name;
    this.nameField = sforceXML.nameField;
    this.namePointing = sforceXML.namePointing;
    this.nillable = sforceXML.nillable;
    this.permissionable = sforceXML.permissionable;
    this.precision = sforceXML.precision;
    this.restrictedPicklist = sforceXML.restrictedPicklist;
    this.scale = sforceXML.scale;
    this.soapType = sforceXML.soapType;
    this.sortable = sforceXML.sortable;
    this.type = sforceXML.type;
    this.unique = sforceXML.unique;
    this.updateable = sforceXML.updateable;
}

//TODO: switch to a constructor
var Connection = {
    connectionId: null, //aka sid_client
    sfconnection: null,
    globalDescribe: null,
	fullDescribes: new Object(),
    lastUsed: Date.now(),
    fetchGlobalDescribe: function(){
        this.globalDescribe = this.sfconnection.describeGlobal();
    },
    getOrganizationSchema: function(){
        //TODO: add better caching (on connection instantiation?)
        if(this.globalDescribe == null)
            this.fetchGlobalDescribe();
        var result = new OrganizationSchema();
        for(var i = 0; i < this.globalDescribe.sobjects.length; i++){
            var describe = this.globalDescribe.sobjects[i];
            var objType = new SObjectType(describe,this.connectionId);
            result.sObjectTypes[objType.name.toLowerCase()] = objType;
        }
        return result;
    },
    getDescribeForId: function(recordId){
        if(this.globalDescribe == null)
            this.fetchGlobalDescribe();
        var result;
        for(var i = 0; i < this.globalDescribe.sobjects.length; i++){
            var describe = this.globalDescribe.sobjects[i];
            if(describe.keyPrefix == recordId.substring(0,3))
                result = describe;
        }
        return result;
    },
	getFieldsForSObject: function(typeName){
		var describe;
		console.log("looking up full describe for "+typeName);
        if(this.fullDescribes[typeName] !== undefined){
			describe = this.fullDescribes[typeName];
		}else {
			describe = this.sfconnection.describeSObject(typeName);
			console.log("Got describe: ",describe);
			this.fullDescribes[typeName] = describe;
		}
		return describe;
	}

}

var cache = {
    cachedConnections: {},
    getConnection: function(context){
        if(context == null || context.sessionId == null || context.sid_Client == null)
            throw "Invalid context";
        var result = this.cachedConnections[context.sid_Client];
        if(result == null){
            result = Object.create(Connection);
            result.connectionId = context.sid_Client;
            this.cachedConnections[result.connectionId] = result;
            result.sfconnection = new sforce.Connection();
            console.log("Setting up API connection with session Id "+context.masterSessionId+" to "+context.sfhost);
            result.sfconnection.init(context.masterSessionId,"https://"+context.sfhost+"/services/Soap/u/25.0");
            UserContext.getUrl = function(path){ return "https://"+context.sfhost+path; }; //hack for apex.js to work
        }else{
            UserContext.getUrl = function(path){ return "https://"+context.sfhost+path; }; //hack for apex.js to work
        }
        result.lastUsed = Date.now();
        return result;
    },
    removeConnection: function(context){
        if(context == null || context.sessionId == null || context.sid_Client == null)
            throw "Invalid context";
        delete this.cachedConnections[context.sid_Client];
    }

};

var cleanupTimer = 900000; // 15 minutes - value is miliseconds between cleanup runs.
setInterval(
    function(){
        //Clear out connections unused for x seconds
        var maxConnectionCacheAge = config.getConfig("connection_cleanup_age") * 60 * 60 * 1000; //config is hrs - need it in ms
        console.log('Running old connection cleanup. Removing entries older than '+maxConnectionCacheAge+' miliseconds.');
        var now = Date.now();
        for(var key in cache.cachedConnections){
            if( (now - maxConnectionCacheAge) >= cache.cachedConnections[key].lastUsed){
                console.log('removing connection '+key+'. Last used '+cache.cachedConnections[key].lastUsed);
                delete cache.cachedConnections[key];
            }
        }
    },
    cleanupTimer
);
<|endoftext|>


<commit_before>var _ = require('./utils.js');

// Breadth-first walk the tree in reverse order
var inverseBreadth = function(node, cb) {
  if(!node.children) return;
  for(var i = node.children.length - 1; i >= 0; i--) {
    cb(node.children[i]);
  };
  _.each(node.children, function(child) {
    inverseBreadth(child, cb);
  });
};

// Clean tree from alternatives
var cleanTree = function(node) {
  if(!node.children) return;
  for(var i = node.children.length - 1; i >= 0; i--) {
    var child = node.children[i];
    if(child.type == 'Alternative') {
      node.children.splice(i, 1);
    } else {
      cleanTree(child);
    }
  };
  return node;
};

var Parser = {
  indentSpaces: 2,

  // Parse the given template source and spit out a traversable tree
  parseTemplate: function(tmpl) {
    var self = this;
    var lines = tmpl.split('\n');
    var lastIndent = 0;
    var lineNum = 0;
    var top = {
      type: 'TOP',
      children: []
    };
    var lastNode = top;
    var stack = [top];
    var slurpyMode = false;
    _.each(lines, function(line) {
      lineNum++;
      var indent = Math.max(0, line.search(/\S/)) / self.indentSpaces;
      // Regard anything below a slurpy tag as text
      if(slurpyMode && indent > lastIndent) {
        var parent = lastNode;
        parent.children.push({
          type: 'Text',
          content: line.slice(lastIndent * self.indentSpaces + self.indentSpaces) + '\n'
        });
        return;
      }
      slurpyMode = false;
      // Remove indentation
      line = line.slice(indent * self.indentSpaces);
      // Ignore comments
      var ci = line.indexOf('//');
      if(ci == 0) return;
      // Ignore empty lines
      if(!line.replace(/\s/g, '').length) return;
      // Push and pop the current parent from the stack
      // as dictated by indentation
      if(indent > lastIndent) {
        if(indent - lastIndent > 1) {
          throw({message: 'Too much indentation', lineNum: lineNum});
        }
        if(lastNode.content) throw({message: 'Elements cannot have both text and children elements', lineNum: lineNum});
        stack.push(lastNode);
      } else if(indent < lastIndent) {
        _.times(lastIndent - indent, function() {
          stack.pop();
        });
      }
      var node = self.parseLine(line);
      // Add the generated node to its parent
      var parent = _.last(stack);
      parent.children.push(node);
      // If we have a multiTags hierarchy -> use rightmost child as lastNode
      var child = node;
      while(child.children && child.children[0]) {
        child = child.children[0];
      };
      lastIndent = indent;
      lastNode = child;
      if(node.slurpy) {
        slurpyMode = true;
      }
    });
    // Squash alternatives into their respective statements
    var previous;
    var alternativeStack = [];
    inverseBreadth(top, function(node) {
      // Collect alternatives from bottom to top
      if(node.type == 'Alternative') {
        alternativeStack.push(node);
      // Attach to next statement we encounter
      } else {
        if(alternativeStack.length) {
          node.alternatives = _.map(alternativeStack.reverse(), function(alt) {
            return alt.children;
          });
          alternativeStack = [];
        }
      }
      previous = node;
    });
    // Remove alternatives
    return cleanTree(top);
  },

  // Convert line into tree node
  parseLine: function(line) {
    // Statement
    if(line.indexOf('{{') == 0) {
      return this.parseStatement(line);
    // Text
    } else if(line.indexOf('|') == 0) {
      return {
        type: 'Text',
        content: line.slice(2) + '\n'
      };
    // Tag definition
    } else {
      return this.parseTag(line);
    }
  },

  // Parse all major components from a tag, including inline content
  parseTag: function(line) {
    var self = this;
    var m = line.match(/(([\w-#\.]+>)*)([\w-]+)?(#([\w-]+))?((\.[\w-]+)*)(\((.*)\))?(\.)?( (.*))?/);
    var multiTags = m[1];
    var tag = m[3] || 'div';
    var id = m[5];
    var classes = m[6] ? m[6].slice(1).split('.') : [];
    var inParens = m[9];
    var dot = m[10];
    var content = m[12];
    var attributes = {};
    var statements = [];
    // Parse attributes
    if(inParens) {
      var attrDefinitions;
      var i = inParens.indexOf('{{');
      if(i > -1) {
        attrDefinitions = inParens.slice(0, i);
        var statementDefinitions = inParens.slice(i, inParens.length);
        statements = self.parseMicroStatements(statementDefinitions);
      } else {
        attrDefinitions = inParens;
      }
      _.each(_.scan(attrDefinitions, /([\w-]+?)=(['"])(.+?)\2\s?/g), function(m) { // '
        var key = m[1];
        var value = m[3];
        attributes[key] = value;
      });
      // _.each(_.scan(statementDefinitions, /{{(\w+)\s+(\w+)\s+(\w+)}}/g), function(m) {
      //   statements[m[2]] = m[3];
      // });
    }
    // Make AST node
    var tag = {
      type: 'HTMLTag',
      tag: tag,
      id: id,
      classes: classes,
      attributes: attributes,
      content: content,
      statements: statements,
      slurpy: !!dot,
      children: []
    };
    // Build a hierarchy from multi tags
    if(multiTags) {
      multiTags = multiTags.slice(0, -1);
      var tags = _.map(multiTags.split('>'), function(t) {
        return self.parseTag(t);
      });
      var top = tags.shift();
      var prev = top;
      _.each(tags, function(t) {
        prev.children.push(t);
        prev = t;
      });
      prev.children.push(tag);
      return top;
    } else {
      return tag;
    }
  },

  parseMicroStatements: function(string) {
    var statements = [];
    _.each(_.scan(string, /{{(\w+)\s+(.*)}}/g), function(m) {
      var statement = m[1];
      var rest = m[2];
      if(statement == 'as') {
        statements.push({statement: 'as', varName: rest});
      } else if(statement == 'action') {
        var m = rest.match(/(\w+)\s+(\w+)/);
        statements.push({statement: 'action', event: m[1], method: m[2]});
      } else {
        throw('Unknown statement: ' + statement);
      }
    });
    return statements;
  },

  // Takes an statement line and creates the approriate node
  parseStatement: function(line) {
    var m;
    var out;
    // if-greater
    if(m = line.match(/{{if\s+(.+)\s+>\s+(.+)\s*}}/)) {
      out = {
        type: 'Statement',
        keyword: 'if-greater',
        path1: m[1],
        path2: m[2]
      };
    // if-equal
    } else if(m = line.match(/{{if\s+(.+)\s+==\s+(.+)\s*}}/)) {
      out = {
        type: 'Statement',
        keyword: 'if-equal',
        path1: m[1],
        path2: m[2]
      };
    // if-not-equal
    } else if(m = line.match(/{{if\s+(.+)\s+!=\s+(.+)\s*}}/)) {
      out = {
        type: 'Statement',
        keyword: 'if-not-equal',
        path1: m[1],
        path2: m[2]
      };
    // if
    } else if(m = line.match(/{{if\s+(.+)}}/)) {
      out = {
        type: 'Statement',
        keyword: 'if',
        path: m[1]
      };
    // for
    } else if(m = line.match(/{{for\s+((\w+)\s+in\s+)?(.+)}}/)) {
      out = {
        type: 'Statement',
        keyword: 'for',
        itemPath: m[2],
        itemsPath: m[3]
      };
    // view
    } else if(m = line.match(/{{view\s+(\w+)?(\(.*\))?}}/)) {
      var parens = m[2];
      var args = parens ? _.map(parens.slice(1, -1).split(','), function(argument) {
        return argument.replace(/\s/g, '');
      }) : [];
      out = {
        type: 'Statement',
        keyword: 'view',
        viewModel: m[1],
        arguments: args
      };
    // import
    } else if(m = line.match(/{{import\s+(\w+)(\(.*\))?}}/)) {
      var parens = m[2];
      var args = {};
      if(parens) {
        _.each(parens.slice(1, -1).split(','), function(argument) {
          var pair = argument.replace(/\s/g, '').split(':');
          var vari = pair[0];
          var expr = pair[1];
          args[vari] = expr;
        });
      }
      out = {
        type: 'Statement',
        keyword: 'import',
        templateName: m[1] + '.tmpl',
        arguments: args
      };
    // content
    } else if(m = line.match(/{{content}}/)) {
      out = {
        type: 'Statement',
        keyword: 'content'
      };
    // client
    } else if(m = line.match(/{{client}}/)) {
      out = {
        type: 'Statement',
        keyword: 'client'
      };
    // route
    } else if(m = line.match(/{{route (.*)}}/)) {
      out = {
        type: 'Statement',
        keyword: 'route',
        path: m[1]
      };
    // =>
    } else if(m = line.match(/{{=>}}/)) {
      out = {
        type: 'Alternative'
      };
    } else {
      throw('Unknown statement: ' + line);
    }
    out.children = [];
    return out;
  }
};

module.exports = Parser;
<commit_msg>Allow empty lines in slurpy text<commit_after>var _ = require('./utils.js');

// Breadth-first walk the tree in reverse order
var inverseBreadth = function(node, cb) {
  if(!node.children) return;
  for(var i = node.children.length - 1; i >= 0; i--) {
    cb(node.children[i]);
  };
  _.each(node.children, function(child) {
    inverseBreadth(child, cb);
  });
};

// Clean tree from alternatives
var cleanTree = function(node) {
  if(!node.children) return;
  for(var i = node.children.length - 1; i >= 0; i--) {
    var child = node.children[i];
    if(child.type == 'Alternative') {
      node.children.splice(i, 1);
    } else {
      cleanTree(child);
    }
  };
  return node;
};

var Parser = {
  indentSpaces: 2,

  // Parse the given template source and spit out a traversable tree
  parseTemplate: function(tmpl) {
    var self = this;
    var lines = tmpl.split('\n');
    var lastIndent = 0;
    var lineNum = 0;
    var top = {
      type: 'TOP',
      children: []
    };
    var lastNode = top;
    var stack = [top];
    var slurpyMode = false;
    _.each(lines, function(line) {
      lineNum++;
      var indent = Math.max(0, line.search(/\S/)) / self.indentSpaces;
      // Regard anything below a slurpy tag as text
      if(slurpyMode && (indent > lastIndent || !line.replace(/\s/g, '').length)) {
        var parent = lastNode;
        parent.children.push({
          type: 'Text',
          content: line.slice(lastIndent * self.indentSpaces + self.indentSpaces) + '\n'
        });
        return;
      }
      slurpyMode = false;
      // Remove indentation
      line = line.slice(indent * self.indentSpaces);
      // Ignore comments
      var ci = line.indexOf('//');
      if(ci == 0) return;
      // Ignore empty lines
      if(!line.replace(/\s/g, '').length) return;
      // Push and pop the current parent from the stack
      // as dictated by indentation
      if(indent > lastIndent) {
        if(indent - lastIndent > 1) {
          throw({message: 'Too much indentation', lineNum: lineNum});
        }
        if(lastNode.content) throw({message: 'Elements cannot have both text and children elements', lineNum: lineNum});
        stack.push(lastNode);
      } else if(indent < lastIndent) {
        _.times(lastIndent - indent, function() {
          stack.pop();
        });
      }
      var node = self.parseLine(line);
      // Add the generated node to its parent
      var parent = _.last(stack);
      parent.children.push(node);
      // If we have a multiTags hierarchy -> use rightmost child as lastNode
      var child = node;
      while(child.children && child.children[0]) {
        child = child.children[0];
      };
      lastIndent = indent;
      lastNode = child;
      if(node.slurpy) {
        slurpyMode = true;
      }
    });
    // Squash alternatives into their respective statements
    var previous;
    var alternativeStack = [];
    inverseBreadth(top, function(node) {
      // Collect alternatives from bottom to top
      if(node.type == 'Alternative') {
        alternativeStack.push(node);
      // Attach to next statement we encounter
      } else {
        if(alternativeStack.length) {
          node.alternatives = _.map(alternativeStack.reverse(), function(alt) {
            return alt.children;
          });
          alternativeStack = [];
        }
      }
      previous = node;
    });
    // Remove alternatives
    return cleanTree(top);
  },

  // Convert line into tree node
  parseLine: function(line) {
    // Statement
    if(line.indexOf('{{') == 0) {
      return this.parseStatement(line);
    // Text
    } else if(line.indexOf('|') == 0) {
      return {
        type: 'Text',
        content: line.slice(2) + '\n'
      };
    // Tag definition
    } else {
      return this.parseTag(line);
    }
  },

  // Parse all major components from a tag, including inline content
  parseTag: function(line) {
    var self = this;
    var m = line.match(/(([\w-#\.]+>)*)([\w-]+)?(#([\w-]+))?((\.[\w-]+)*)(\((.*)\))?(\.)?( (.*))?/);
    var multiTags = m[1];
    var tag = m[3] || 'div';
    var id = m[5];
    var classes = m[6] ? m[6].slice(1).split('.') : [];
    var inParens = m[9];
    var dot = m[10];
    var content = m[12];
    var attributes = {};
    var statements = [];
    // Parse attributes
    if(inParens) {
      var attrDefinitions;
      var i = inParens.indexOf('{{');
      if(i > -1) {
        attrDefinitions = inParens.slice(0, i);
        var statementDefinitions = inParens.slice(i, inParens.length);
        statements = self.parseMicroStatements(statementDefinitions);
      } else {
        attrDefinitions = inParens;
      }
      _.each(_.scan(attrDefinitions, /([\w-]+?)=(['"])(.+?)\2\s?/g), function(m) { // '
        var key = m[1];
        var value = m[3];
        attributes[key] = value;
      });
      // _.each(_.scan(statementDefinitions, /{{(\w+)\s+(\w+)\s+(\w+)}}/g), function(m) {
      //   statements[m[2]] = m[3];
      // });
    }
    // Make AST node
    var tag = {
      type: 'HTMLTag',
      tag: tag,
      id: id,
      classes: classes,
      attributes: attributes,
      content: content,
      statements: statements,
      slurpy: !!dot,
      children: []
    };
    // Build a hierarchy from multi tags
    if(multiTags) {
      multiTags = multiTags.slice(0, -1);
      var tags = _.map(multiTags.split('>'), function(t) {
        return self.parseTag(t);
      });
      var top = tags.shift();
      var prev = top;
      _.each(tags, function(t) {
        prev.children.push(t);
        prev = t;
      });
      prev.children.push(tag);
      return top;
    } else {
      return tag;
    }
  },

  parseMicroStatements: function(string) {
    var statements = [];
    _.each(_.scan(string, /{{(\w+)\s+(.*)}}/g), function(m) {
      var statement = m[1];
      var rest = m[2];
      if(statement == 'as') {
        statements.push({statement: 'as', varName: rest});
      } else if(statement == 'action') {
        var m = rest.match(/(\w+)\s+(\w+)/);
        statements.push({statement: 'action', event: m[1], method: m[2]});
      } else {
        throw('Unknown statement: ' + statement);
      }
    });
    return statements;
  },

  // Takes an statement line and creates the approriate node
  parseStatement: function(line) {
    var m;
    var out;
    // if-greater
    if(m = line.match(/{{if\s+(.+)\s+>\s+(.+)\s*}}/)) {
      out = {
        type: 'Statement',
        keyword: 'if-greater',
        path1: m[1],
        path2: m[2]
      };
    // if-equal
    } else if(m = line.match(/{{if\s+(.+)\s+==\s+(.+)\s*}}/)) {
      out = {
        type: 'Statement',
        keyword: 'if-equal',
        path1: m[1],
        path2: m[2]
      };
    // if-not-equal
    } else if(m = line.match(/{{if\s+(.+)\s+!=\s+(.+)\s*}}/)) {
      out = {
        type: 'Statement',
        keyword: 'if-not-equal',
        path1: m[1],
        path2: m[2]
      };
    // if
    } else if(m = line.match(/{{if\s+(.+)}}/)) {
      out = {
        type: 'Statement',
        keyword: 'if',
        path: m[1]
      };
    // for
    } else if(m = line.match(/{{for\s+((\w+)\s+in\s+)?(.+)}}/)) {
      out = {
        type: 'Statement',
        keyword: 'for',
        itemPath: m[2],
        itemsPath: m[3]
      };
    // view
    } else if(m = line.match(/{{view\s+(\w+)?(\(.*\))?}}/)) {
      var parens = m[2];
      var args = parens ? _.map(parens.slice(1, -1).split(','), function(argument) {
        return argument.replace(/\s/g, '');
      }) : [];
      out = {
        type: 'Statement',
        keyword: 'view',
        viewModel: m[1],
        arguments: args
      };
    // import
    } else if(m = line.match(/{{import\s+(\w+)(\(.*\))?}}/)) {
      var parens = m[2];
      var args = {};
      if(parens) {
        _.each(parens.slice(1, -1).split(','), function(argument) {
          var pair = argument.replace(/\s/g, '').split(':');
          var vari = pair[0];
          var expr = pair[1];
          args[vari] = expr;
        });
      }
      out = {
        type: 'Statement',
        keyword: 'import',
        templateName: m[1] + '.tmpl',
        arguments: args
      };
    // content
    } else if(m = line.match(/{{content}}/)) {
      out = {
        type: 'Statement',
        keyword: 'content'
      };
    // client
    } else if(m = line.match(/{{client}}/)) {
      out = {
        type: 'Statement',
        keyword: 'client'
      };
    // route
    } else if(m = line.match(/{{route (.*)}}/)) {
      out = {
        type: 'Statement',
        keyword: 'route',
        path: m[1]
      };
    // =>
    } else if(m = line.match(/{{=>}}/)) {
      out = {
        type: 'Alternative'
      };
    } else {
      throw('Unknown statement: ' + line);
    }
    out.children = [];
    return out;
  }
};

module.exports = Parser;
<|endoftext|>


<commit_before>#!/usr/bin/env node

var commands = require("../lib/commands");
var meow     = require("meow");
var init     = require("../lib/init");

var cli = meow([
  "Usage",
  "  $ lerna [command]",
  "",
  "Commands:",
  "  bootstrap  Link together local packages and npm install remaining package dependencies",
  "  publish    Publish updated packages to npm",
  "  updated    Check which packages have changed since the last release",
  ""
]);

var commandName = cli.input[0];
var command = commands[commandName];
var config = init(commandName, process.cwd());

command(config);
<commit_msg>Show help when invalid command<commit_after>#!/usr/bin/env node

var commands = require("../lib/commands");
var chalk    = require("chalk");
var meow     = require("meow");
var init     = require("../lib/init");

var cli = meow([
  "Usage",
  "  $ lerna [command]",
  "",
  "Commands:",
  "  bootstrap  Link together local packages and npm install remaining package dependencies",
  "  publish    Publish updated packages to npm",
  "  updated    Check which packages have changed since the last release",
  ""
]);

var commandName = cli.input[0];
var command = commands[commandName];

if (!command) {
  console.log();
  console.log(chalk.red("  Invalid command: " + chalk.bold(commandName)));
  cli.showHelp();
}

var config = init(commandName, process.cwd());

command(config);
<|endoftext|>


<commit_before>const models = require('./models')
const moment = require('moment')
const i18n = require('i18n')
const DeadlineMail = require('./modules/mail/deadline')
const TaskMail = require('./modules/mail/task')

i18n.configure({
  directory: process.env.NODE_ENV !== 'production' ? `${__dirname}/locales` : `${__dirname}/locales/result`,
  locales: process.env.NODE_ENV !== 'production' ? ['en'] : ['en', 'br'],
  defaultLocale: 'en',
  updateFiles: false
})

i18n.init()

const Report = {
  montlyBounties: async () => {
    const tasks = await models.Task.findAll({ where: {
      value: {
        $gt: 0
      }
    },
    include: [ models.User ]
    })
    // eslint-disable-next-line no-console
    // console.log('tasks from cron job weekly bounties', tasks)
    if (tasks[0]) {
      const taskSort = tasks.sort((ta, tb) => {
        return Math.abs(new Date(ta.created_at) - new Date(tb.created_at))
      })
      const taskSortTwo = taskSort.map((t) => {
        return [t.createdAt, t.createdAt.toLocaleString('default', { month: 'long' }), t.url]
      })
      // eslint-disable-next-line no-console
      console.log('tasks', taskSortTwo)
    }
    return new Error('no issues found')
  },
  latestTasks: async () => {
    const tasks = await models.Task.findAll({
      where: {
        assigned: {
          $eq: null
        },
        status: {
          $eq: 'open'
        }
      },
      limit: 5,
      order: [['id', 'DESC']],
      include: [ models.User ]
    })
    // eslint-disable-next-line no-console
    console.log('tasks from cron job latest tasks', tasks)
    if (tasks[0]) {
      TaskMail.weeklyLatest({ tasks })
    }
    return tasks
  },
  rememberDeadline: async () => {
    const tasks = await models.Task.findAll({ where: {
      status: 'in_progress',
      deadline: {
        $lt: moment(new Date()).format(),
        $gt: moment(new Date()).subtract(2, 'days').format()
      }
    },
    include: [ models.User ]
    })
    // eslint-disable-next-line no-console
    console.log('tasks from cron job to remember deadline', tasks)
    if (tasks[0]) {
      tasks.map(async t => {
        if (t.assigned) {
          if (t.dataValues && t.assigned) {
            const userAssigned = await models.Assign.findAll({ where: { id: t.assigned }, include: [models.User] })
            if (userAssigned[0].dataValues) {
              DeadlineMail.deadlineEndOwner(t.User.dataValues, t.dataValues, t.User.name || t.User.username)
              DeadlineMail.deadlineEndAssigned(userAssigned[0].dataValues.User, t.dataValues, userAssigned[0].dataValues.User.dataValues.name)
            }
          }
        }
      })
    }
    return tasks
  }
}

Report.montlyBounties()

module.exports = { Report }
<commit_msg>making report as a module<commit_after>const models = require('./models')
const moment = require('moment')
const i18n = require('i18n')
const DeadlineMail = require('./modules/mail/deadline')
const TaskMail = require('./modules/mail/task')

i18n.configure({
  directory: process.env.NODE_ENV !== 'production' ? `${__dirname}/locales` : `${__dirname}/locales/result`,
  locales: process.env.NODE_ENV !== 'production' ? ['en'] : ['en', 'br'],
  defaultLocale: 'en',
  updateFiles: false
})

i18n.init()

const Report = {
  montlyBounties: async () => {
    const tasks = await models.Task.findAll({ where: {
      value: {
        $gt: 0
      }
    },
    include: [ models.User ]
    })
    // eslint-disable-next-line no-console
    // console.log('tasks from cron job weekly bounties', tasks)
    if (tasks[0]) {
      const taskSort = tasks.sort((ta, tb) => {
        return Math.abs(new Date(ta.created_at) - new Date(tb.created_at))
      })
      const taskSortTwo = taskSort.map((t) => {
        return [t.createdAt, t.createdAt.toLocaleString('default', { month: 'long' }), t.url]
      })
      // eslint-disable-next-line no-console
      console.log('tasks', taskSortTwo)
    }
    return new Error('no issues found')
  },
  latestTasks: async () => {
    const tasks = await models.Task.findAll({
      where: {
        assigned: {
          $eq: null
        },
        status: {
          $eq: 'open'
        }
      },
      limit: 5,
      order: [['id', 'DESC']],
      include: [ models.User ]
    })
    // eslint-disable-next-line no-console
    console.log('tasks from cron job latest tasks', tasks)
    if (tasks[0]) {
      TaskMail.weeklyLatest({ tasks })
    }
    return tasks
  },
  rememberDeadline: async () => {
    const tasks = await models.Task.findAll({ where: {
      status: 'in_progress',
      deadline: {
        $lt: moment(new Date()).format(),
        $gt: moment(new Date()).subtract(2, 'days').format()
      }
    },
    include: [ models.User ]
    })
    // eslint-disable-next-line no-console
    console.log('tasks from cron job to remember deadline', tasks)
    if (tasks[0]) {
      tasks.map(async t => {
        if (t.assigned) {
          if (t.dataValues && t.assigned) {
            const userAssigned = await models.Assign.findAll({ where: { id: t.assigned }, include: [models.User] })
            if (userAssigned[0].dataValues) {
              DeadlineMail.deadlineEndOwner(t.User.dataValues, t.dataValues, t.User.name || t.User.username)
              DeadlineMail.deadlineEndAssigned(userAssigned[0].dataValues.User, t.dataValues, userAssigned[0].dataValues.User.dataValues.name)
            }
          }
        }
      })
    }
    return tasks
  }
}

module.exports = { Report }
<|endoftext|>


<commit_before>"use strict";

const utf8Encoder = new TextEncoder();
const utf8Decoder = new TextDecoder("utf-8");

const HASH_ALGORITHMS = new Map([
	["sha256", {byteLength: 32, cryptoID: "SHA-256"}],
	["sha384", {byteLength: 48, cryptoID: "SHA-384"}],
	["sha512", {byteLength: 64, cryptoID: "SHA-512"}],
]);

const DEFAULT_SOURCE_HASH_ALGO = "sha256";// see HASH_ALGORITHMS

// Use search({}) to get all bookmark nodes instead of tree traversal (using browser.bookmarks.getTree() + https://en.wikipedia.org/wiki/Tree_traversal)
let gettingBookmarkletSourceHashes = browser.bookmarks.search({}).then(filterBookmarklets).then(bookmarklets => getBookmarkletSourceHashes(bookmarklets, DEFAULT_SOURCE_HASH_ALGO));
let randomBytes = new Uint8Array(HASH_ALGORITHMS.get(DEFAULT_SOURCE_HASH_ALGO).byteLength);// will be filled with random bytes to generate fake source hashes

// See https://tools.ietf.org/html/rfc5234#appendix-B.1
// and https://www.w3.org/TR/CSP2/#source-list-syntax
const CSP_HEADER = "Content-Security-Policy"
const WSP = /[ \t]+/;// space or horizontal space
const EMPTY_TOKEN = /^[ \t]*$/;
/*
^
[\t ]*                                        // *WSP
([a-zA-Z0-9\-]+)                              // directive-name
(?:[\t ]([\t\x20-\x2b\x2d-\x3A\x3C-\x7E]*))?  // [ WSP directive-value ]
$
*/
const CSP_DIRECTIVE_TOKEN = /^[\t ]*([a-zA-Z0-9\-]+)(?:[\t ]([\t\x20-\x2b\x2d-\x3A\x3C-\x7E]*))?$/;
const CSP_SCRIPT_SRC = "script-src";
const CSP_DEFAULT_SRC = "default-src";
// Some keywords:
const CSP_SRC_NONE = "'none'";
const CSP_UNSAFE_INLINE = "'unsafe-inline'";

/**
 * Get source hash (as it's used in CSP) from hash bytes (generated from a digest function
 * @see https://www.w3.org/TR/CSP2/#hash_source
 */
function getSourceHashFromHashBytes(hashBytes, algo = "sha512"){
	hashBytes = new Uint8Array(hashBytes);
	let hashAlgo = HASH_ALGORITHMS.get(algo);
	if(!hashAlgo){
		throw new Error(`Unsupported hash algorithm "${algo}"`);
	}

	if(hashBytes.length != hashAlgo.byteLength){
		throw new Error(`Invalid hash length: ${hashBytes.length}B (required: ${hashAlgo.byteLength}B)`);
	}

	let valueChars = hashBytes.reduce((chars, byte) => (chars += String.fromCharCode(byte), chars), "");// to UTF-8
	let base64Value = btoa(valueChars);
	return "'" + algo + "-" + base64Value + "'";// "'" hash-algo "-" base64-value "'"
}

/**
 * Get source hash from source
 * @param source String Source text to get hash
 * @param algo Hash algorithm. All CSP2 allowed algorithms: "sha256" / "sha384" / "sha512". See https://www.w3.org/TR/CSP2/#hash_algo
 * @example getSourceHash("alert('Hello, world.');"); // resolve as "'sha256-qznLcsROx4GACP2dm0UCKCzCG+HiZ1guq6ZZDob/Tng='"
 * @returns Promise
 */
function getSourceHash(source, algo = "sha512"){
	let hashAlgo = HASH_ALGORITHMS.get(algo);
	if(!hashAlgo){
		throw new Error(`Unsupported hash algorithm "${algo}"`);
	}

	let sourceBytes = utf8Encoder.encode(source);
	return crypto.subtle.digest(hashAlgo.cryptoID, sourceBytes).then(valueBuffer => getSourceHashFromHashBytes(valueBuffer, algo));
}

/**
 * Get only bookmarklets (starting with javascript protocol)
 */
function filterBookmarklets(bookmarkNodes){
	let bookmarkletNodes = [];
	for(let node of bookmarkNodes){
		let url = node.url;
		if(url && url.startsWith("javascript:")){
			bookmarkletNodes.push(node);
		}
	}
	return bookmarkletNodes;
}

/**
 * Get source hashes of given bookmarks and the algorithm to use
 * @param bookmarklets Array
 * @param algo String
 * @see https://html.spec.whatwg.org/multipage/browsers.html#navigating-across-documents:javascript-protocol
 * @returns Promise Resolved with an new array of hashes
 */
function getBookmarkletSourceHashes(bookmarklets, algo = "sha512"){
	let hashesPromises = [];
	for(let bookmarklet of bookmarklets){
		let url = bookmarklet.url;
		let source = "";
		// folder or non "javascript:" URI = empty source
		if(url && url.startsWith("javascript:")){
			source = decodeURIComponent(url.slice(11));
		}

		hashesPromises.push(getSourceHash(source, algo));
	}
	//hashesPromises.push(getSourceHash("alert('Hello, world.');", algo));
	return Promise.all(hashesPromises);
}

/**
 * Get bookmarklet + random source hashes
 * @returns Array The provided sourceHashes array with new random hashes (see count) added to the end
 */
function addRandomSourceHashes(sourceHashes, algo = "sha512", count = 10){
	let hashAlgo = HASH_ALGORITHMS.get(algo);
	if(!hashAlgo){
		throw new Error(`Unsupported hash algorithm "${algo}"`);
	}

	if(randomBytes.length != hashAlgo.byteLength){
		throw new Error(`randomBytes length don't match algo: ${randomBytes.length}B (required: ${hashAlgo.byteLength}B)`);
	}

	for(let i = 0; i < count; i++){
		crypto.getRandomValues(randomBytes);// fill with random to fake a hash bytes
		sourceHashes.push(getSourceHashFromHashBytes(randomBytes, algo));
	}

	return sourceHashes;
}

/**
 * Suffle array elements. Aka randomize, random permutation. Use Fisher–Yates shuffle
 * @see https://bost.ocks.org/mike/shuffle/
 * @see https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle
 * @returns Array The provided array with all elements shuffled
 */
function shuffle(array) {
	let index = array.length;

	// While there remain elements to shuffle…
	while (index) {
		// Pick a remaining element…
		let destIndex = Math.floor(Math.random() * index--);

		// And swap it with the current element.
		let current = array[index];
		array[index] = array[destIndex];
		array[destIndex] = current;
	}

	return array;
}

/*
 * Rewrite the CSP header.
 * @see https://www.w3.org/TR/CSP2/#policy-syntax
 * @see https://tools.ietf.org/html/rfc5234 RFC 5234 - Augmented BNF for Syntax Specifications: ABNF
 */
function rewriteCSPHeader(details){
	let responseHeaders = details.responseHeaders;
	let cspHeaderNameLower = CSP_HEADER.toLowerCase();
	let cspHeader = responseHeaders.find(header => header.name.toLowerCase() == cspHeaderNameLower);// Find the first CSP header, case insensitive
	let response = {responseHeaders};

	// Ignore the rest if no CSP header is founded
	if(!cspHeader){
		return response;
	}

	return gettingBookmarkletSourceHashes.then(sourceHashes => {
		// If no hashes, ignore below
		if(sourceHashes.length == 0){
			return response;
		}

		// Add random hashes + suffle
		sourceHashes = shuffle(addRandomSourceHashes(sourceHashes, DEFAULT_SOURCE_HASH_ALGO, Math.round(Math.random() * 50)));

		let value = cspHeader.value;//or cspHeader.binaryValue?
		let directives = value.split(";").reduce((directives, token) => {
			// Don't generate parse error

			// Empty token, skip
			if(EMPTY_TOKEN.test(token)){
				return directives;
			}

			let directiveParseResult = CSP_DIRECTIVE_TOKEN.exec(token);
			// Invalid directive
			if(!directiveParseResult){
				return directives;
			}

			let [, directiveName = "", directiveValue = ""] = directiveParseResult;
			directiveName = directiveName.toLowerCase();

			if(directiveName != "" && !directives.has(directiveName)){
				directives.set(directiveName, directiveValue);
			}

			return directives;
		}, new Map());

		// If script-src is not define, should use the same value as default-src https://www.w3.org/TR/CSP2/#directive-default-src
		if(!directives.has(CSP_SCRIPT_SRC)){
			// Ignore below, because both default-src and script-src are not defined (ex: "content-security-policy: upgrade-insecure-requests"), means all sources are allowed
			if(!directives.has(CSP_DEFAULT_SRC)){
				return response;
			}

			directives.set(CSP_SCRIPT_SRC, directives.get(CSP_DEFAULT_SRC) || "");
		}

		// Parse script-src source list
		let sourceList = new Set(directives.get(CSP_SCRIPT_SRC).split(WSP));
		sourceList.delete("");// remove empty sources (because start and/or end with WSP)
		sourceList.delete(CSP_SRC_NONE);// remove none source, because we have sources

		// Some keywords allow javascript scheme
		// If both unsafe-inline and a nonce or a hash are defined, all inline scripts must have a nonce or a hash,
		// see https://www.w3.org/TR/CSP2/#directive-script-src

		if(sourceList.has("*") || sourceList.has(CSP_UNSAFE_INLINE)){
			return response;
		}

		// Append all hashes to script-src directive source list
		for(let sourceHash of sourceHashes){
			sourceList.add(sourceHash);
		}

		directives.set(CSP_SCRIPT_SRC, Array.from(sourceList.values()).join(" "));// update script-src directive
		let directiveList = [];
		directives.forEach((value, name) => directiveList.push(`${name} ${value}`));//reduce map to array

		console.info(`CSP update on ${details.url}`);
		console.log(`${cspHeader.value}\n→\n${directiveList.join(";")}`)

		cspHeader.value = directiveList.join(";");// update the CSP header's value

		return response;
	});
}

/*
Add rewriteCSPHeader as a listener to onHeadersReceived, only for the target page.
Make it "blocking" so we can modify the response headers.
*/
browser.webRequest.onHeadersReceived.addListener(
	rewriteCSPHeader,
	{
		urls: ["*://*/*"],
		types: ["main_frame", "sub_frame"]
	},
	["blocking", "responseHeaders"]
);<commit_msg>Detect when a least one hash or one none is defined<commit_after>"use strict";

const utf8Encoder = new TextEncoder();
const utf8Decoder = new TextDecoder("utf-8");

const HASH_ALGORITHMS = new Map([
	["sha256", {byteLength: 32, cryptoID: "SHA-256"}],
	["sha384", {byteLength: 48, cryptoID: "SHA-384"}],
	["sha512", {byteLength: 64, cryptoID: "SHA-512"}],
]);

const DEFAULT_SOURCE_HASH_ALGO = "sha256";// see HASH_ALGORITHMS

// Use search({}) to get all bookmark nodes instead of tree traversal (using browser.bookmarks.getTree() + https://en.wikipedia.org/wiki/Tree_traversal)
let gettingBookmarkletSourceHashes = browser.bookmarks.search({}).then(filterBookmarklets).then(bookmarklets => getBookmarkletSourceHashes(bookmarklets, DEFAULT_SOURCE_HASH_ALGO));
let randomBytes = new Uint8Array(HASH_ALGORITHMS.get(DEFAULT_SOURCE_HASH_ALGO).byteLength);// will be filled with random bytes to generate fake source hashes

// See https://tools.ietf.org/html/rfc5234#appendix-B.1
// and https://www.w3.org/TR/CSP2/#source-list-syntax
const CSP_HEADER = "Content-Security-Policy"
const WSP = /[ \t]+/;// space or horizontal space
const EMPTY_TOKEN = /^[ \t]*$/;
/*
^
[\t ]*                                        // *WSP
([a-zA-Z0-9\-]+)                              // directive-name
(?:[\t ]([\t\x20-\x2b\x2d-\x3A\x3C-\x7E]*))?  // [ WSP directive-value ]
$
*/
const CSP_DIRECTIVE_TOKEN = /^[\t ]*([a-zA-Z0-9\-]+)(?:[\t ]([\t\x20-\x2b\x2d-\x3A\x3C-\x7E]*))?$/;
const CSP_SCRIPT_SRC = "script-src";
const CSP_DEFAULT_SRC = "default-src";
// Some keywords:
const CSP_SRC_NONE = "'none'";
const CSP_UNSAFE_INLINE = "'unsafe-inline'";

/**
 * Get source hash (as it's used in CSP) from hash bytes (generated from a digest function
 * @see https://www.w3.org/TR/CSP2/#hash_source
 */
function getSourceHashFromHashBytes(hashBytes, algo = "sha512"){
	hashBytes = new Uint8Array(hashBytes);
	let hashAlgo = HASH_ALGORITHMS.get(algo);
	if(!hashAlgo){
		throw new Error(`Unsupported hash algorithm "${algo}"`);
	}

	if(hashBytes.length != hashAlgo.byteLength){
		throw new Error(`Invalid hash length: ${hashBytes.length}B (required: ${hashAlgo.byteLength}B)`);
	}

	let valueChars = hashBytes.reduce((chars, byte) => (chars += String.fromCharCode(byte), chars), "");// to UTF-8
	let base64Value = btoa(valueChars);
	return "'" + algo + "-" + base64Value + "'";// "'" hash-algo "-" base64-value "'"
}

/**
 * Get source hash from source
 * @param source String Source text to get hash
 * @param algo Hash algorithm. All CSP2 allowed algorithms: "sha256" / "sha384" / "sha512". See https://www.w3.org/TR/CSP2/#hash_algo
 * @example getSourceHash("alert('Hello, world.');"); // resolve as "'sha256-qznLcsROx4GACP2dm0UCKCzCG+HiZ1guq6ZZDob/Tng='"
 * @returns Promise
 */
function getSourceHash(source, algo = "sha512"){
	let hashAlgo = HASH_ALGORITHMS.get(algo);
	if(!hashAlgo){
		throw new Error(`Unsupported hash algorithm "${algo}"`);
	}

	let sourceBytes = utf8Encoder.encode(source);
	return crypto.subtle.digest(hashAlgo.cryptoID, sourceBytes).then(valueBuffer => getSourceHashFromHashBytes(valueBuffer, algo));
}

/**
 * Get only bookmarklets (starting with javascript protocol)
 */
function filterBookmarklets(bookmarkNodes){
	let bookmarkletNodes = [];
	for(let node of bookmarkNodes){
		let url = node.url;
		if(url && url.startsWith("javascript:")){
			bookmarkletNodes.push(node);
		}
	}
	return bookmarkletNodes;
}

/**
 * Get source hashes of given bookmarks and the algorithm to use
 * @param bookmarklets Array
 * @param algo String
 * @see https://html.spec.whatwg.org/multipage/browsers.html#navigating-across-documents:javascript-protocol
 * @returns Promise Resolved with an new array of hashes
 */
function getBookmarkletSourceHashes(bookmarklets, algo = "sha512"){
	let hashesPromises = [];
	for(let bookmarklet of bookmarklets){
		let url = bookmarklet.url;
		let source = "";
		// folder or non "javascript:" URI = empty source
		if(url && url.startsWith("javascript:")){
			source = decodeURIComponent(url.slice(11));
		}

		hashesPromises.push(getSourceHash(source, algo));
	}
	//hashesPromises.push(getSourceHash("alert('Hello, world.');", algo));
	return Promise.all(hashesPromises);
}

/**
 * Get bookmarklet + random source hashes
 * @returns Array The provided sourceHashes array with new random hashes (see count) added to the end
 */
function addRandomSourceHashes(sourceHashes, algo = "sha512", count = 10){
	let hashAlgo = HASH_ALGORITHMS.get(algo);
	if(!hashAlgo){
		throw new Error(`Unsupported hash algorithm "${algo}"`);
	}

	if(randomBytes.length != hashAlgo.byteLength){
		throw new Error(`randomBytes length don't match algo: ${randomBytes.length}B (required: ${hashAlgo.byteLength}B)`);
	}

	for(let i = 0; i < count; i++){
		crypto.getRandomValues(randomBytes);// fill with random to fake a hash bytes
		sourceHashes.push(getSourceHashFromHashBytes(randomBytes, algo));
	}

	return sourceHashes;
}

/**
 * Suffle array elements. Aka randomize, random permutation. Use Fisher–Yates shuffle
 * @see https://bost.ocks.org/mike/shuffle/
 * @see https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle
 * @returns Array The provided array with all elements shuffled
 */
function shuffle(array) {
	let index = array.length;

	// While there remain elements to shuffle…
	while (index) {
		// Pick a remaining element…
		let destIndex = Math.floor(Math.random() * index--);

		// And swap it with the current element.
		let current = array[index];
		array[index] = array[destIndex];
		array[destIndex] = current;
	}

	return array;
}

/*
 * Rewrite the CSP header.
 * @see https://www.w3.org/TR/CSP2/#policy-syntax
 * @see https://tools.ietf.org/html/rfc5234 RFC 5234 - Augmented BNF for Syntax Specifications: ABNF
 */
function rewriteCSPHeader(details){
	let responseHeaders = details.responseHeaders;
	let cspHeaderNameLower = CSP_HEADER.toLowerCase();
	let cspHeader = responseHeaders.find(header => header.name.toLowerCase() == cspHeaderNameLower);// Find the first CSP header, case insensitive
	let response = {responseHeaders};

	// Ignore the rest if no CSP header is founded
	if(!cspHeader){
		return response;
	}

	return gettingBookmarkletSourceHashes.then(sourceHashes => {
		// If no hashes, ignore below
		if(sourceHashes.length == 0){
			return response;
		}

		// Add random hashes + suffle
		sourceHashes = shuffle(addRandomSourceHashes(sourceHashes, DEFAULT_SOURCE_HASH_ALGO, Math.round(Math.random() * 50)));

		let value = cspHeader.value;//or cspHeader.binaryValue?
		let directives = value.split(";").reduce((directives, token) => {
			// Don't generate parse error

			// Empty token, skip
			if(EMPTY_TOKEN.test(token)){
				return directives;
			}

			let directiveParseResult = CSP_DIRECTIVE_TOKEN.exec(token);
			// Invalid directive
			if(!directiveParseResult){
				return directives;
			}

			let [, directiveName = "", directiveValue = ""] = directiveParseResult;
			directiveName = directiveName.toLowerCase();

			if(directiveName != "" && !directives.has(directiveName)){
				directives.set(directiveName, directiveValue);
			}

			return directives;
		}, new Map());

		// If script-src is not define, should use the same value as default-src https://www.w3.org/TR/CSP2/#directive-default-src
		if(!directives.has(CSP_SCRIPT_SRC)){
			// Ignore below, because both default-src and script-src are not defined (ex: "content-security-policy: upgrade-insecure-requests"), means all sources are allowed
			if(!directives.has(CSP_DEFAULT_SRC)){
				return response;
			}

			directives.set(CSP_SCRIPT_SRC, directives.get(CSP_DEFAULT_SRC) || "");
		}

		// Parse script-src source list
		let sourceList = new Set(directives.get(CSP_SCRIPT_SRC).split(WSP));
		sourceList.delete("");// remove empty sources (because start and/or end with WSP)
		sourceList.delete(CSP_SRC_NONE);// remove none source, because we have sources
		let hasHashOrNonce;// if at least one hash or one nonce if defined
		{
			let prefixes = ["'nonce-"];
			HASH_ALGORITHMS.forEach((value, key) => prefixes.push(`'${key}-`));

			hasHashOrNonce = !!Array.from(sourceList).find(source => prefixes.find(prefix => source.startsWith(prefix)));
		}

		// Some keywords allow javascript scheme
		// If both unsafe-inline and a nonce or a hash are defined, all inline scripts must have a nonce or a hash,
		// see https://www.w3.org/TR/CSP2/#directive-script-src
		if(sourceList.has(CSP_UNSAFE_INLINE) && !hasHashOrNonce){
			return response;
		}

		// Append all hashes to script-src directive source list
		for(let sourceHash of sourceHashes){
			sourceList.add(sourceHash);
		}

		directives.set(CSP_SCRIPT_SRC, Array.from(sourceList).join(" "));// update script-src directive
		let directiveList = [];
		directives.forEach((value, name) => directiveList.push(`${name} ${value}`));//reduce map to array

		console.info(`CSP update for ${details.url}:\n${cspHeader.value}\n→\n${directiveList.join(";")}`);

		cspHeader.value = directiveList.join(";");// update the CSP header's value

		return response;
	});
}

/*
Add rewriteCSPHeader as a listener to onHeadersReceived, only for the target page.
Make it "blocking" so we can modify the response headers.
*/
browser.webRequest.onHeadersReceived.addListener(
	rewriteCSPHeader,
	{
		urls: ["*://*/*"],
		types: ["main_frame", "sub_frame"]
	},
	["blocking", "responseHeaders"]
);<|endoftext|>


<commit_before>#!/usr/bin/env node

var Transaction = require('../src/js/ripple/transaction').Transaction;

var argv = process.argv.slice(2);

var verbose;
var secret;
var tx_json;

if (~argv.indexOf('-v')){
  argv.splice(argv.indexOf('-v'), 1);
  verbose = true;
}

secret  = argv.shift();
tx_json = argv.shift();

if (tx_json === '-') {
  read_input(ready);
} else {
  ready();
}

function read_input(callback) {
  tx_json = '';
  process.stdin.on('data', function(data) { tx_json += data; });
  process.stdin.on('end', callback);
  process.stdin.resume();
}

function ready() {
  var valid_arguments = secret && tx_json;

  if (!valid_arguments) {
    console.error('Invalid arguments\n');
    print_usage();
  } else {
    var valid_json = true;

    try {
      tx_json = JSON.parse(tx_json);
    } catch(exception) {
      valid_json = false;
    }

    if (!valid_json) {
      console.error('Invalid JSON\n');
      print_usage();
    } else {
      sign_transaction();
    }
  }
}

function print_usage() {
  console.log(
    'Usage: rsign.js <secret> <json>\n\n',
    'Example: rsign.js ssq55ueDob4yV3kPVnNQLHB6icwpC',
    JSON.stringify({
      TransactionType: 'Payment',
      Account: 'r3P9vH81KBayazSTrQj6S25jW6kDb779Gi',
      Destination: 'r3kmLJN5D28dHuH8vZNUZpMC43pEHpaocV',
      Amount: '200000000',
      Fee: '10',
      Sequence: 1
    })
    );
};

function sign_transaction() {
  var tx = new Transaction();

  tx.tx_json = tx_json;
  tx._secret = secret;
  tx.complete();

  var unsigned_blob = tx.serialize().to_hex();
  var unsigned_hash = tx.signingHash();
  tx.sign();

  if (verbose) {
    var sim = { };

    sim.tx_blob         = tx.serialize().to_hex();
    sim.tx_json         = tx.tx_json;
    sim.tx_signing_hash = unsigned_hash;
    sim.tx_unsigned     = unsigned_blob;

    console.log(JSON.stringify(sim, null, 2));
  } else {
    console.log(tx.serialize().to_hex());
  }
};

// vim:sw=2:sts=2:ts=8:et
<commit_msg>Correct usage example with surrounding apostrophes<commit_after>#!/usr/bin/env node

var Transaction = require('../src/js/ripple/transaction').Transaction;

var argv = process.argv.slice(2);

var verbose;
var secret;
var tx_json;

if (~argv.indexOf('-v')){
  argv.splice(argv.indexOf('-v'), 1);
  verbose = true;
}

secret  = argv.shift();
tx_json = argv.shift();

if (tx_json === '-') {
  read_input(ready);
} else {
  ready();
}

function read_input(callback) {
  tx_json = '';
  process.stdin.on('data', function(data) { tx_json += data; });
  process.stdin.on('end', callback);
  process.stdin.resume();
}

function ready() {
  var valid_arguments = secret && tx_json;

  if (!valid_arguments) {
    console.error('Invalid arguments\n');
    print_usage();
  } else {
    var valid_json = true;

    try {
      tx_json = JSON.parse(tx_json);
    } catch(exception) {
      valid_json = false;
    }

    if (!valid_json) {
      console.error('Invalid JSON\n');
      print_usage();
    } else {
      sign_transaction();
    }
  }
}

function print_usage() {
  console.log(
    'Usage: rsign.js <secret> <json>\n\n',
    'Example: rsign.js ssq55ueDob4yV3kPVnNQLHB6icwpC','\''+
    JSON.stringify({
      TransactionType: 'Payment',
      Account: 'r3P9vH81KBayazSTrQj6S25jW6kDb779Gi',
      Destination: 'r3kmLJN5D28dHuH8vZNUZpMC43pEHpaocV',
      Amount: '200000000',
      Fee: '10',
      Sequence: 1
    })+'\''
    );
};

function sign_transaction() {
  var tx = new Transaction();

  tx.tx_json = tx_json;
  tx._secret = secret;
  tx.complete();

  var unsigned_blob = tx.serialize().to_hex();
  var unsigned_hash = tx.signingHash();
  tx.sign();

  if (verbose) {
    var sim = { };

    sim.tx_blob         = tx.serialize().to_hex();
    sim.tx_json         = tx.tx_json;
    sim.tx_signing_hash = unsigned_hash;
    sim.tx_unsigned     = unsigned_blob;

    console.log(JSON.stringify(sim, null, 2));
  } else {
    console.log(tx.serialize().to_hex());
  }
};

// vim:sw=2:sts=2:ts=8:et
<|endoftext|>


<commit_before>// create array to which all scraped pages are pushed
var hnPostList = [];

// check for HN comments when page is navigated to
function checkPageURL(tabId, changeInfo, tab) {
	if (changeInfo && changeInfo.status == "loading") {
		// get current tab URL
		var currentURL = tab.url

		// check if in list of stored tabs
		if ( ! notInList( currentURL, hnPostList ) ) {
			console.log("HN page!");
		}
	}
}

// tab change listener runs URL checking function
chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) {
	checkPageURL(tabId, changeInfo, tab);
} );

// Add listener for pageaction
chrome.pageAction.onClicked.addListener(function(tab) {
	function navigateTab(commentsPage) {
		// create URL of comments page
		var commentsURL = "http://news.ycombinator.com/" + commentsPage;
		// navigate tab to new url
		chrome.tabs.update(tabId, {url: commentsURL});
	}

	// get tabId from tab object
	var tabId = tab.id,
		commentsID,
		commentsURL;

	// access local storage
	chrome.storage.local.get(null, function(savedData) {
		// get discussion page from local storage
		var commentsPage = savedData.discussionURL;
		// pass page URL to function to naviage the tab
		navigateTab(commentsPage);
	});
});

function notInList( item, list  ) {
	// loop over list
	for ( var i = 0; i < list.length; i++ ) {
		// get linkURL item from currently selected
		var currentURL = list[i].linkURL;

		// return false if equivalent
		if ( currentURL == item ) {
			return 0
		}
	}
	return 1
}

// listens for messages passed when chrome storage is altered
chrome.runtime.onMessage.addListener(function(message, sender, sendResponse) {

    // check the correct message is being recieved
    if ( message.scrapeArray ) {

    	var newPostList = message.scrapeArray;

    	for ( var i = 0; i < newPostList.length; i++ ) {

    		var post = newPostList[i],  // current item
    			postURL = post.linkURL;	// get url

    		// add to hnPostList if not already present
    		if( notInList( postURL, hnPostList ) ) {
    			hnPostList.push(post);
    		}

    	}
    }

    console.log(hnPostList);
});<commit_msg>posInList returns index if item found and null if not.<commit_after>// create array to which all scraped pages are pushed
var hnPostList = [];

// check for HN comments when page is navigated to
function checkPageURL(tabId, changeInfo, tab) {
	if (changeInfo && changeInfo.status == "loading") {
		// get current tab URL
		var currentURL = tab.url

		// check if in list of stored tabs
		if ( ! posInList( currentURL, hnPostList ) ) {
			console.log("HN page!");
		}
	}
}

// tab change listener runs URL checking function
chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) {
	checkPageURL(tabId, changeInfo, tab);
} );

// Add listener for pageaction
chrome.pageAction.onClicked.addListener(function(tab) {
	function navigateTab(commentsPage) {
		// create URL of comments page
		var commentsURL = "http://news.ycombinator.com/" + commentsPage;
		// navigate tab to new url
		chrome.tabs.update(tabId, {url: commentsURL});
	}

	// get tabId from tab object
	var tabId = tab.id,
		commentsID,
		commentsURL;

	// access local storage
	chrome.storage.local.get(null, function(savedData) {
		// get discussion page from local storage
		var commentsPage = savedData.discussionURL;
		// pass page URL to function to naviage the tab
		navigateTab(commentsPage);
	});
});

function posInList( item, list  ) {
	// loop over list
	for ( var i = 0; i < list.length; i++ ) {
		// get linkURL item from currently selected
		var currentURL = list[i].linkURL;

		// return false if equivalent
		if ( currentURL == item ) {
			return i
		}
	}
	return null
}

// listens for messages passed when chrome storage is altered
chrome.runtime.onMessage.addListener(function(message, sender, sendResponse) {

    // check the correct message is being recieved
    if ( message.scrapeArray ) {

    	var newPostList = message.scrapeArray;

    	for ( var i = 0; i < newPostList.length; i++ ) {

    		var post = newPostList[i],  // current item
    			postURL = post.linkURL;	// get url

    		// add to hnPostList if not already present
    		if( posInList( postURL, hnPostList ) ) {
    			hnPostList.push(post);
    		}

    	}
    }

    console.log(hnPostList);
});<|endoftext|>


<commit_before>#!/usr/bin/env node

var dirExists = require('../libs/directories').dirExists;
var walk = require('../libs/directories').walk;
var lib = require('../libs');
require('colors');

var rootDir = process.argv[2];

if (dirExists(rootDir)) {
  var astData = walk(rootDir);
  var data = lib(astData);

  printName();
  console.log('');

  var modules = Object.keys(data);

  modules.forEach(printList);

  console.log('');
} else {
  console.log(rootDir + ' is not a valid directory!');
}

function printList(key) {
  console.log(key.toUpperCase().green);
  console.log('----------------------------'.green);
  countsToArray(data[key]).forEach(printLine);
  console.log('');
}

function printLine(count) {
  console.log(count.key.trim().yellow + ': ' + count.val.toString().red);
}

// {}->[{}]
function countsToArray(obj) {
  return Object.keys(obj)
    .reduce(countsToObjects(obj), [])
    .sort(sortByVal);
}

function sortByVal(a, b) {
  return b.val - a.val;
}

function countsToObjects(obj) {
  return function(acc, key) {
    acc.push({ key: key, val: obj[key] });
    return acc;
  };
}

function printName() {
  console.log(' ____   __   ____  ____  _  _      ____  ____  __  ____  ____ '.magenta);
  console.log('/ ___) / _\\ / ___)/ ___)( \\/ )___ / ___)(_  _)/ _\\(_  _)/ ___)'.magenta);
  console.log('\\___ \\/    \\\\___ \\\\___ \\ )  /(___)\\___ \\  )( /    \\ )(  \\___ \\'.magenta);
  console.log('(____/\\_/\\_/(____/(____/(__/      (____/ (__)\\_/\\_/(__) (____/'.magenta);
}<commit_msg>added number spaces to report<commit_after>#!/usr/bin/env node

var dirExists = require('../libs/directories').dirExists;
var walk = require('../libs/directories').walk;
var lib = require('../libs');
require('colors');

var rootDir = process.argv[2];

if (dirExists(rootDir)) {
  var astData = walk(rootDir);
  var data = lib(astData);

  printName();
  console.log('');

  var modules = Object.keys(data);

  modules.forEach(printList);

  console.log('');
} else {
  console.log(rootDir + ' is not a valid directory!');
}

function printList(key) {
  console.log(key.toUpperCase().green);
  console.log('----------------------------'.green);
  countsToArray(data[key]).forEach(printLine);
  console.log('');
}

function printLine(count) {
  var keyStr = count.key.trim() + ':';
  console.log(keyStr.yellow + getSpaces(keyStr) + count.val.toString().red);
}

function getSpaces(str) {
  var result = '';

  for(var i = 0; i < 40 - str.length; i++) {
    result += ' ';
  }

  return result;
}

// {}->[{}]
function countsToArray(obj) {
  return Object.keys(obj)
    .reduce(countsToObjects(obj), [])
    .sort(sortByVal);
}

function sortByVal(a, b) {
  return b.val - a.val;
}

function countsToObjects(obj) {
  return function(acc, key) {
    acc.push({ key: key, val: obj[key] });
    return acc;
  };
}

function printName() {
  console.log(' ____   __   ____  ____  _  _      ____  ____  __  ____  ____ '.magenta);
  console.log('/ ___) / _\\ / ___)/ ___)( \\/ )___ / ___)(_  _)/ _\\(_  _)/ ___)'.magenta);
  console.log('\\___ \\/    \\\\___ \\\\___ \\ )  /(___)\\___ \\  )( /    \\ )(  \\___ \\'.magenta);
  console.log('(____/\\_/\\_/(____/(____/(__/      (____/ (__)\\_/\\_/(__) (____/'.magenta);
}<|endoftext|>


<commit_before>// ################################################################################################################# //
//
// Parser
//
// ################################################################################################################# //

yate.parser = {

    _patterns: {},
    _skippers: {},

    _skipper: null

};

// ----------------------------------------------------------------------------------------------------------------- //
// Init
// ----------------------------------------------------------------------------------------------------------------- //

yate.parser.init = function(grammar) {
    this._addTokens(grammar.tokens);
    this._addKeywords(grammar.keywords);
    this._addRules(grammar.rules);
    this._addSkippers(grammar.skippers);
};

// ----------------------------------------------------------------------------------------------------------------- //

yate.parser._addTokens = function(tokens) {
    tokens = tokens || {};
    for (var id in tokens) {
        this._addToken(id, tokens[id]);
    }
};

yate.parser._addToken = function(id, token) {
    token = this._makeToken(id, token);
    this._patterns[id.toUpperCase()] = token;
    return token;
};

yate.parser._makeToken = function(id, token) {
    if (typeof token == 'string') {
        var l = token.length;
        return function() {
            if (this.current(l) == token) {
                this.next(l);
                this.skip();
                return token;
            }
            this.error('Expected token ' + id);
        };
    } else if (token instanceof RegExp) {
        return function() {
            var r = token.exec(this.current());
            if (r) {
                r = r[0];
                var l = r.length;
                if (l) {
                    this.next(l);
                    this.skip();
                }
                return r;
            }
            this.error('Expected token ' + id);
        };
    } else if (typeof token == 'function') {
        return token;
    }
};

// ----------------------------------------------------------------------------------------------------------------- //

yate.parser._addKeywords = function(keywords) {
    keywords = keywords || [];
    for (var i = 0, l = keywords.length; i < l; i++) {
        var keyword = keywords[i];
        this._addToken(keyword, new RegExp('^' + keyword + '\\b'));
    };
};

// ----------------------------------------------------------------------------------------------------------------- //

yate.parser._addRules = function(rules) {
    rules = rules || {};
    for (var id in rules) {
        this._addRule(id, rules[id]);
    }
};

yate.parser._addRule = function(id, rule) {
    if (typeof rule == 'function') {
        this._patterns[id] = this._makeRule(id, rule);
    } else {
        this._patterns[id] = this._makeRule(id, rule.rule, rule.options);
    }
};

yate.parser._makeRule = function(id, rule, options) {
    options = options || {};

    var that = this;
    var wrapper = function() {
        var _skipper = that.setSkipper(options.skipper);

        var ast = that._makeAST(id);
        var args = Array.prototype.slice.call(arguments);
        args.unshift(ast);
        var r = rule.apply(that, args);

        that.setSkipper(_skipper);

        return (r !== undefined) ? r : ast;
    };
    wrapper.options = options;

    return wrapper;
};

yate.parser._makeAST = function(id) {
    var ast = yate.AST.make(id);
    ast.where = this._getPos();
    return ast;
};

// ----------------------------------------------------------------------------------------------------------------- //

yate.parser._addSkippers = function(skippers) {
    skippers = skippers || {};
    for (var id in skippers) {
        this._addSkipper(id, skippers[id]);
    }
};

yate.parser._addSkipper = function(id, skipper) {
    this._skippers[id] = this._makeSkipper(id, skipper);
};

yate.parser._makeSkipper = function(id, skipper) {
    if (skipper instanceof RegExp) {
        return function() {
            var r = skipper.exec(this.current());
            if (r) {
                r = r[0];
                var l = r.length;
                if (l) {
                    this.next(l);
                    return true; // Что-то поскипали.
                }
            }
        };
    } else if (typeof skipper == 'function') {
        return skipper;
    }
};

// ----------------------------------------------------------------------------------------------------------------- //
// Open
// ----------------------------------------------------------------------------------------------------------------- //

yate.parser.open = function(o) {
    var input = o.input;

    if (!input) {
        this._filename = o.filename;
        input = require('fs').readFileSync(o.filename, 'utf-8');
    }

    this._lines = input.split('\n');
    this._x = 0;
    this._y = 0;
    this._current = this._lines[0];
};

// ----------------------------------------------------------------------------------------------------------------- //
// Input manipulations
// ----------------------------------------------------------------------------------------------------------------- //

yate.parser.current = function(n) {
    return (n) ? this._current.substr(0, n) : this._current;
};

yate.parser.next = function(n) {
    this._x += n;
    this._current = this._current.substr(n);
};

yate.parser.nextLine = function(n) {
    this._x = 0;
    this._y += (n || 1);
    this._current = this._lines[this._y];
};

yate.parser.isEOL = function() {
    return /^\s*(\/\/.*)?$/.test(this.current());
};

yate.parser.eol = function() {
    if (this.isEOL()) {
        this.nextLine();
        this.skip();
    } else {
        this.error('EOL expected');
    }
};

yate.parser.isEOF = function() {
    return (this._current === undefined);
};

// ----------------------------------------------------------------------------------------------------------------- //
// Errors
// ----------------------------------------------------------------------------------------------------------------- //

yate.parser.error = function(error) {
    error = error || 'Unknown error';

    var msg = 'SYNTAX ERROR: ' + error + '\n';
    msg += this._where();

    throw msg;
};

// Этот метод нужен для того, чтобы показать, что правило не смогло правильно сматчиться и нужно делать backtrace.
yate.parser.backtrace = function(error) {
    error = error || this.id + ' expected';

    throw 'PARSE ERROR: ' + error;
};

// ----------------------------------------------------------------------------------------------------------------- //

yate.parser._where = function(pos) {
    pos = pos || this;

    var where = 'at (' + (pos._x + 1) + ', ' + (pos._y + 1) + ')';
    if (pos._filename) {
        where += ' in ' + pos._filename;
    }
    var line = this._lines[pos._y] || '';
    where += ':\n' + line + '\n' + Array(pos._x + 1).join('-') + '^';

    return where;
};

// ----------------------------------------------------------------------------------------------------------------- //
yate.parser.skip = function(id) {
    id = id || this._skipper;
    var skipper = this._skippers[id];
    var r = skipper.call(this);
    return r;
};

// ----------------------------------------------------------------------------------------------------------------- //

yate.parser._$ = function(id) {
    var pattern = this._patterns[id];
    if (!pattern) {
        pattern = this._addToken(id, id);
    }
    return pattern;
};

// ----------------------------------------------------------------------------------------------------------------- //
// Test / Match
// ----------------------------------------------------------------------------------------------------------------- //

yate.parser.test = function(id) {
    var state = this._getState();
    var r = true;
    try {
        this._$(id).call(this);
    } catch (e) {
        r = false;
        // console.log(e);
    }
    this._setState(state);

    return r;
};

yate.parser.testAny = function(ids) {
    for (var i = 0, l = ids.length; i < l; i++) {
        var id = ids[i];
        if (this.test(id)) {
            return id;
        }
    }
    return false;
};

yate.parser.testAll = function(ids) {
    var state = this._getState();
    var r = true;
    try {
        for (var i = 0, l = ids.length; i < l; i++) {
            this._$(ids[i]).call(this);
        }
    } catch (e) {
        r = false;
        // console.log(e);
    }
    this._setState(state);

    return r;
};

// match('rule')
// match('rule', 42)
// match({ rule: 'rule', options: {} }, 42)

yate.parser.match = function(id) {
    var options = {};
    if (typeof id == 'object') {
        options = id.options;
        id = id.rule
    }
    var skipper = this.setSkipper(options.skipper);
    var args = Array.prototype.slice.call(arguments, 1);
    var r = this._$(id).apply(this, args);
    this.setSkipper(skipper);
    return r;
};

yate.parser.matchAny = function(ids) {
    for (var i = 0, l = ids.length; i < l; i++) {
        var id = ids[i];
        if (this.test(id)) {
            return this.match(id);
        }
    }

    this.error('Nothing matched');
};

// ----------------------------------------------------------------------------------------------------------------- //
// Getters / Setters
// ----------------------------------------------------------------------------------------------------------------- //

yate.parser.getSkipper = function() {
    return this._skipper;
};

yate.parser.setSkipper = function(id) {
    var current = this._skipper;
    if (id) {
        this._skipper = id;
        this.skip();
    }
    return current;
};

// ----------------------------------------------------------------------------------------------------------------- //

yate.parser._setState = function(state) {
    this._setPos(state.pos);
    this.setSkipper(state.skipper);
};

yate.parser._getState = function() {
    return {
        pos: this._getPos(),
        skipper: this.getSkipper()
    };
};

// ----------------------------------------------------------------------------------------------------------------- //

yate.parser._setPos = function(pos) {
    this._x = pos._x;
    this._y = pos._y;
    this._current = this._lines[this._y].substr(this._x);
};

yate.parser._getPos = function() {
    return {
        _x: this._x,
        _y: this._y
    };
};

// ----------------------------------------------------------------------------------------------------------------- //

<commit_msg>Memoization: кэшируем вызовы test(), с match() что-то не задалось, пока что так<commit_after>// ################################################################################################################# //
//
// Parser
//
// ################################################################################################################# //

yate.parser = {

    _patterns: {},
    _skippers: {},

    _skipper: null

};

// ----------------------------------------------------------------------------------------------------------------- //
// Init
// ----------------------------------------------------------------------------------------------------------------- //

yate.parser.init = function(grammar) {
    this._addTokens(grammar.tokens);
    this._addKeywords(grammar.keywords);
    this._addRules(grammar.rules);
    this._addSkippers(grammar.skippers);
};

// ----------------------------------------------------------------------------------------------------------------- //

yate.parser._addTokens = function(tokens) {
    tokens = tokens || {};
    for (var id in tokens) {
        this._addToken(id, tokens[id]);
    }
};

yate.parser._addToken = function(id, token) {
    token = this._makeToken(id, token);
    this._patterns[id.toUpperCase()] = token;
    return token;
};

yate.parser._makeToken = function(id, token) {
    if (typeof token == 'string') {
        var l = token.length;
        return function() {
            if (this.current(l) == token) {
                this.next(l);
                this.skip();
                return token;
            }
            this.error('Expected token ' + id);
        };
    } else if (token instanceof RegExp) {
        return function() {
            var r = token.exec(this.current());
            if (r) {
                r = r[0];
                var l = r.length;
                if (l) {
                    this.next(l);
                    this.skip();
                }
                return r;
            }
            this.error('Expected token ' + id);
        };
    } else if (typeof token == 'function') {
        return token;
    }
};

// ----------------------------------------------------------------------------------------------------------------- //

yate.parser._addKeywords = function(keywords) {
    keywords = keywords || [];
    for (var i = 0, l = keywords.length; i < l; i++) {
        var keyword = keywords[i];
        this._addToken(keyword, new RegExp('^' + keyword + '\\b'));
    };
};

// ----------------------------------------------------------------------------------------------------------------- //

yate.parser._addRules = function(rules) {
    rules = rules || {};
    for (var id in rules) {
        this._addRule(id, rules[id]);
    }
};

yate.parser._addRule = function(id, rule) {
    if (typeof rule == 'function') {
        this._patterns[id] = this._makeRule(id, rule);
    } else {
        this._patterns[id] = this._makeRule(id, rule.rule, rule.options);
    }
};

yate.parser._makeRule = function(id, rule, options) {
    options = options || {};

    var that = this;
    var wrapper = function() {
        var _skipper = that.setSkipper(options.skipper);

        var ast = that._makeAST(id);
        var args = Array.prototype.slice.call(arguments);
        args.unshift(ast);
        var r = rule.apply(that, args);

        that.setSkipper(_skipper);

        return (r !== undefined) ? r : ast;
    };
    wrapper.options = options;

    return wrapper;
};

yate.parser._makeAST = function(id) {
    var ast = yate.AST.make(id);
    ast.where = this._getPos();
    return ast;
};

// ----------------------------------------------------------------------------------------------------------------- //

yate.parser._addSkippers = function(skippers) {
    skippers = skippers || {};
    for (var id in skippers) {
        this._addSkipper(id, skippers[id]);
    }
};

yate.parser._addSkipper = function(id, skipper) {
    this._skippers[id] = this._makeSkipper(id, skipper);
};

yate.parser._makeSkipper = function(id, skipper) {
    if (skipper instanceof RegExp) {
        return function() {
            var r = skipper.exec(this.current());
            if (r) {
                r = r[0];
                var l = r.length;
                if (l) {
                    this.next(l);
                    return true; // Что-то поскипали.
                }
            }
        };
    } else if (typeof skipper == 'function') {
        return skipper;
    }
};

// ----------------------------------------------------------------------------------------------------------------- //
// Open
// ----------------------------------------------------------------------------------------------------------------- //

yate.parser.open = function(o) {
    var input = o.input;

    if (!input) {
        this._filename = o.filename;
        input = require('fs').readFileSync(o.filename, 'utf-8');
    }

    this._lines = input.split('\n');
    this._x = 0;
    this._y = 0;
    this._current = this._lines[0];
};

// ----------------------------------------------------------------------------------------------------------------- //
// Input manipulations
// ----------------------------------------------------------------------------------------------------------------- //

yate.parser.current = function(n) {
    return (n) ? this._current.substr(0, n) : this._current;
};

yate.parser.next = function(n) {
    this._x += n;
    this._current = this._current.substr(n);
};

yate.parser.nextLine = function(n) {
    this._x = 0;
    this._y += (n || 1);
    this._current = this._lines[this._y];
};

yate.parser.isEOL = function() {
    return /^\s*(\/\/.*)?$/.test(this.current());
};

yate.parser.eol = function() {
    if (this.isEOL()) {
        this.nextLine();
        this.skip();
    } else {
        this.error('EOL expected');
    }
};

yate.parser.isEOF = function() {
    return (this._current === undefined);
};

// ----------------------------------------------------------------------------------------------------------------- //
// Errors
// ----------------------------------------------------------------------------------------------------------------- //

yate.parser.error = function(error) {
    error = error || 'Unknown error';

    var msg = 'SYNTAX ERROR: ' + error + '\n';
    msg += this._where();

    throw msg;
};

// Этот метод нужен для того, чтобы показать, что правило не смогло правильно сматчиться и нужно делать backtrace.
yate.parser.backtrace = function(error) {
    error = error || this.id + ' expected';

    throw 'PARSE ERROR: ' + error;
};

// ----------------------------------------------------------------------------------------------------------------- //

yate.parser._where = function(pos) {
    pos = pos || this;

    var where = 'at (' + (pos._x + 1) + ', ' + (pos._y + 1) + ')';
    if (pos._filename) {
        where += ' in ' + pos._filename;
    }
    var line = this._lines[pos._y] || '';
    where += ':\n' + line + '\n' + Array(pos._x + 1).join('-') + '^';

    return where;
};

yate.parser._whereKey = function() {
    return this._x + '|' + this._y;
};

// ----------------------------------------------------------------------------------------------------------------- //
yate.parser.skip = function(id) {
    id = id || this._skipper;
    var skipper = this._skippers[id];
    var r = skipper.call(this);
    return r;
};

// ----------------------------------------------------------------------------------------------------------------- //

yate.parser._$ = function(id) {
    var pattern = this._patterns[id];
    if (!pattern) {
        pattern = this._addToken(id, id);
    }
    return pattern;
};

// ----------------------------------------------------------------------------------------------------------------- //
// Test / Match
// ----------------------------------------------------------------------------------------------------------------- //

yate.parser._cache = {};

yate.parser.test = function(id) {
    var key = this._whereKey() + '|' + id;
    var cached = this._cache[key];
    if (cached !== undefined) { return cached; }

    var state = this._getState();
    var r = true;
    try {
        this._$(id).call(this);
    } catch (e) {
        r = false;
        // console.log(e);
    }
    this._setState(state);

    this._cache[key] = r;

    return r;
};

yate.parser.testAny = function(ids) {
    for (var i = 0, l = ids.length; i < l; i++) {
        var id = ids[i];
        if (this.test(id)) {
            return id;
        }
    }
    return false;
};

yate.parser.testAll = function(ids) {
    var state = this._getState();
    var r = true;
    try {
        for (var i = 0, l = ids.length; i < l; i++) {
            this._$(ids[i]).call(this);
        }
    } catch (e) {
        r = false;
        // console.log(e);
    }
    this._setState(state);

    return r;
};

// match('rule')
// match('rule', 42)
// match({ rule: 'rule', options: {} }, 42)

yate.parser.match = function(id) {
    var options = {};
    if (typeof id == 'object') {
        options = id.options;
        id = id.rule
    }

    var skipper = this.setSkipper(options.skipper);
    var args = Array.prototype.slice.call(arguments, 1);
    var r = this._$(id).apply(this, args);
    this.setSkipper(skipper);

    return r;
};

yate.parser.matchAny = function(ids) {
    for (var i = 0, l = ids.length; i < l; i++) {
        var id = ids[i];
        if (this.test(id)) {
            return this.match(id);
        }
    }

    this.error('Nothing matched');
};

// ----------------------------------------------------------------------------------------------------------------- //
// Getters / Setters
// ----------------------------------------------------------------------------------------------------------------- //

yate.parser.getSkipper = function() {
    return this._skipper;
};

yate.parser.setSkipper = function(id) {
    var current = this._skipper;
    if (id) {
        this._skipper = id;
        this.skip();
    }
    return current;
};

// ----------------------------------------------------------------------------------------------------------------- //

yate.parser._setState = function(state) {
    this._setPos(state.pos);
    this.setSkipper(state.skipper);
};

yate.parser._getState = function() {
    return {
        pos: this._getPos(),
        skipper: this.getSkipper()
    };
};

// ----------------------------------------------------------------------------------------------------------------- //

yate.parser._setPos = function(pos) {
    this._x = pos._x;
    this._y = pos._y;
    this._current = this._lines[this._y].substr(this._x);
};

yate.parser._getPos = function() {
    return {
        _x: this._x,
        _y: this._y
    };
};

// ----------------------------------------------------------------------------------------------------------------- //

<|endoftext|>


<commit_before>import React from 'react';

import Select, { components } from 'react-select';
import {
  SortableContainer,
  SortableElement,
  sortableHandle,
} from 'react-sortable-hoc';
import { colourOptions } from './docs/data';

function arrayMove(array, from, to) {
  array = array.slice();
  array.splice(to < 0 ? array.length + to : to, 0, array.splice(from, 1)[0]);
  return array;
}

const SortableMultiValue = SortableElement(props => {
  // this prevents the menu from being opened/closed when the user clicks
  // on a value to begin dragging it. ideally, detecting a click (instead of
  // a drag) would still focus the control and toggle the menu, but that
  // requires some magic with refs that are out of scope for this example
  const onMouseDown = e => {
    e.preventDefault();
    e.stopPropagation();
  };
  const innerProps = { ...props.innerProps, onMouseDown };
  return <components.MultiValue {...props} innerProps={innerProps} />;
});

const SortableMultiValueLabel = sortableHandle(props => (
  <components.MultiValueLabel {...props} />
));

const SortableSelect = SortableContainer(Select);

export default function MultiSelectSort() {
  const [selected, setSelected] = React.useState([
    colourOptions[4],
    colourOptions[5],
  ]);

  const onChange = selectedOptions => setSelected(selectedOptions);

  const onSortEnd = ({ oldIndex, newIndex }) => {
    const newValue = arrayMove(selected, oldIndex, newIndex);
    setSelected(newValue);
    console.log(
      'Values sorted:',
      newValue.map(i => i.value)
    );
  };

  return (
    <SortableSelect
      useDragHandle
      // react-sortable-hoc props:
      axis="xy"
      onSortEnd={onSortEnd}
      distance={4}
      // small fix for https://github.com/clauderic/react-sortable-hoc/pull/352:
      getHelperDimensions={({ node }) => node.getBoundingClientRect()}
      // react-select props:
      isMulti
      options={colourOptions}
      value={selected}
      onChange={onChange}
      components={{
        MultiValue: SortableMultiValue,
        MultiValueLabel: SortableMultiValueLabel,
      }}
      closeMenuOnSelect={false}
    />
  );
}
<commit_msg>fix data reference<commit_after>import React from 'react';

import Select, { components } from 'react-select';
import {
  SortableContainer,
  SortableElement,
  sortableHandle,
} from 'react-sortable-hoc';
import { colourOptions } from '../data';

function arrayMove(array, from, to) {
  array = array.slice();
  array.splice(to < 0 ? array.length + to : to, 0, array.splice(from, 1)[0]);
  return array;
}

const SortableMultiValue = SortableElement(props => {
  // this prevents the menu from being opened/closed when the user clicks
  // on a value to begin dragging it. ideally, detecting a click (instead of
  // a drag) would still focus the control and toggle the menu, but that
  // requires some magic with refs that are out of scope for this example
  const onMouseDown = e => {
    e.preventDefault();
    e.stopPropagation();
  };
  const innerProps = { ...props.innerProps, onMouseDown };
  return <components.MultiValue {...props} innerProps={innerProps} />;
});

const SortableMultiValueLabel = sortableHandle(props => (
  <components.MultiValueLabel {...props} />
));

const SortableSelect = SortableContainer(Select);

export default function MultiSelectSort() {
  const [selected, setSelected] = React.useState([
    colourOptions[4],
    colourOptions[5],
  ]);

  const onChange = selectedOptions => setSelected(selectedOptions);

  const onSortEnd = ({ oldIndex, newIndex }) => {
    const newValue = arrayMove(selected, oldIndex, newIndex);
    setSelected(newValue);
    console.log(
      'Values sorted:',
      newValue.map(i => i.value)
    );
  };

  return (
    <SortableSelect
      useDragHandle
      // react-sortable-hoc props:
      axis="xy"
      onSortEnd={onSortEnd}
      distance={4}
      // small fix for https://github.com/clauderic/react-sortable-hoc/pull/352:
      getHelperDimensions={({ node }) => node.getBoundingClientRect()}
      // react-select props:
      isMulti
      options={colourOptions}
      value={selected}
      onChange={onChange}
      components={{
        MultiValue: SortableMultiValue,
        MultiValueLabel: SortableMultiValueLabel,
      }}
      closeMenuOnSelect={false}
    />
  );
}
<|endoftext|>