<commit_before>;var Plates = (typeof process !== 'undefined' && typeof process.title !== 'undefined') ? exports : {};

!function(exports) {

  var _toString = Object.prototype.toString,
      isArray = Array.isArray || function (o) {
        return _toString.call(o) === "[object Array]";
      };

  var Merge = function Merge() {};

  Merge.prototype = {

    tag: new RegExp([
      '<',
      '(/?)', // 2 - is closing
      '([-:\\w]+)', // 3 - name
      '((?:[-\\w]+(?:', '=',
      '(?:\\w+|["|\'](?:.*)["|\']))?)*)', // 4 - attributes
      '(/?)', // 5 - is self-closing
      '>'
    ].join('\\s*')),

    attr: /([\-\w]*)=(?:["\']([\-\.\w\s\/:;&#]*)["\'])/gi,

    hasClass: function(str, className) {
      return str.indexOf(className) > -1;
    },

    iterate: function(html, value, components, tagname, key) {

      var output  = '',
          segment = html.slice(
            html.search(components.input),
            html.lastIndexOf(tagname) + tagname.length + 1
          ),
          data = {},
          that = this;

      // Is it an array?
      if (isArray(value)) {

        // Yes: set the output to the result of iterating through the array
        for (var i = 0, l = value.length; i < l; i++) {

          // If there is a key, then we have a simple object and
          // must construct a simple object to use as the data
          if (key) {
            data[key] = value[i];
          } else {
            data = value[i];
          }

          output += this.bind(segment, data);

        }

        // TODO : Figure out why Plates is appending closing tags when
        // not required
        return output = output.slice(0, -(tagname.length + 3));

      }

      // Is it an object?
      else if (typeof value === 'object') {

        // We need to refine the selection now that we know we're dealing with
        // nested objects
        segment = segment.slice(components.input.length, -(tagname.length + 3));
        return output += this.bind(segment, value);

      }

      return value;

    },

    bind: function bind(html, data, map) {

      if (isArray(data)) {
        var output = '';
        for (var i = 0, l = data.length; i<l; i++) {
          output += this.bind(html, data[i], map);
        }
        return output;
      }

      html = html || '';
      data = data || {};

      var that = this;

      var openers = 0,
          components,
          attributes,
          mappings = map && map.mappings,
          intag = false,
          tagname = '',
          isClosing = false,
          isSelfClosing = false,
          matchmode = false,
          createAttribute = map && map.conf && map.conf.create,
          closing,
          tagbody;

      var c,
          buffer = '',
          left;

      for (var i = 0, l = html.length; i < l; i++) {

        c = html[i];

        if (c === '!' && intag && !matchmode) {
          intag = false;
          buffer += html.slice(left, i+1);
        }
        else if (c === '<' && !intag) {
          closing = true;
          intag = true;
          left = i;
        }
        else if (c === '>' && intag) {

          intag = false;
          tagbody = html.slice(left, i+1);

          components = this.tag.exec(tagbody);

          if(!components) { continue; }

          isClosing = components[1];
          tagname = components[2];
          attributes = components[3];
          isSelfClosing = components[4];

          if (matchmode) {

            //
            // and its a closing.
            //
            if (!!isClosing) {

              if (openers <= 0) {
                matchmode = false;
              }
              else {
                --openers;
              }
            }
            //
            // and its not a self-closing tag
            //
            else if (!isSelfClosing) {
              ++openers;
            }
          }

          if (attributes && !isClosing && !matchmode) {

            //
            // if there is a match in progress and
            //
            if (mappings && mappings.length > 0) {

              for (var ii = mappings.length - 1; ii >= 0; ii--) {

                var setAttribute = false
                  , shouldSetAttribute = mappings[ii].re && attributes.match(mappings[ii].re);

                tagbody = tagbody.replace(this.attr, function(str, key, value, a) {

                  var newdata = mappings[ii].dataKey ? data[mappings[ii].dataKey] : data[key];

                  if (shouldSetAttribute && mappings[ii].replace !== key) {
                    return str;
                  }
                  else if(shouldSetAttribute) {
                    setAttribute = true;
                    return key + '="' + (newdata || '') + '"';
                  }
                  else if (!mappings[ii].replace && mappings[ii].attribute === key) {

                    if (mappings[ii].value === value || that.hasClass(value, mappings[ii].value || mappings.conf.where === key)) {

                      var v = data[mappings[ii].dataKey];

                      newdata = isArray(v) ? that.iterate(html, v, components, tagname, value)
                        : tagbody + (typeof v === 'object' ? that.iterate(html, v, components, tagname, value) : newdata);

                      buffer += newdata || '';

                      matchmode = true;

                    }
                  }

                  return str;

                });

                if (createAttribute && shouldSetAttribute && !setAttribute) {
                  var spliced = isSelfClosing? 2 : 1;
                  var close = isSelfClosing? '/>': '>';
                  var left = tagbody.substr(0, tagbody.length - spliced);
                  if (left[left.length - 1] == ' ') {
                    left = left.substr(0, left.length - 1);
                    if (isSelfClosing) {
                      close = ' ' + close;
                    }
                  }
                  tagbody = [
                    left,
                    ' ',
                    mappings[ii].replace,
                    '="',
                    data[mappings[ii].dataKey],
                    '"',
                    close
                  ].join('');
                }
              }
            }
            else {

              //
              // if there is no map, we are just looking to match
              // the specified id to a data key in the data object.
              //
              tagbody.replace(
                this.attr,
                function (attr, key, value, idx) {
                  if (key === map && map.conf.where || 'id' && data[value]) {
                    var v      = data[value],
                        output = (isArray(v) || typeof v === 'object') ? that.iterate(html, v, components, tagname, value) : v;

                    buffer += isArray(v) ? output : tagbody + output;
                    matchmode = true;
                  }
                }
              );
            }
          }

          //
          // if there is currently no match in progress
          // just write the tagbody to the buffer.
          //
          if (!matchmode) {
            buffer += tagbody;
          }

        }
        else if (!intag && !matchmode) {

          //
          // currently not inside a tag and there is no
          // match in progress, we can write the char to
          // the buffer.
          //
          buffer += c;
        }

      }
      return buffer;
    }

  };

  var Mapper = function Mapper(conf) {
    if (!(this instanceof Mapper)) { return new Mapper(conf); }
    this.mappings = [];
    this.conf = conf || {};
  };

  function last(newitem) {
    if (newitem) {
      this.mappings.push({});
    }
    var m = this.mappings[this.mappings.length-1];
    if (m.attribute && m.value && m.dataKey && m.replace) {
      m.re = new RegExp(m.attribute + '=([\'"]?)' + m.value + '\\1');
    } else {
      delete m.re;
    }
    return m;
  }

  Mapper.prototype = {
    use: function(val) {
      last.call(this).dataKey = val;
      return last.call(this) && this;
    },
    to: function(val) {
      return this.use(val);
    },
    where: function(val) {
      last.call(this, true).attribute = val;
      return last.call(this) && this;
    },
    class: function(val) {
      return this.where('class').is(val);
    },
    is: function(val) {
      last.call(this).value = val;
      return last.call(this) && this;
    },
    insert: function(val) {
      var l = last.call(this);
      l.replace = l.attribute;
      l.dataKey = val;
      return last.call(this) && this;
    },
    as: function(val) {
      last.call(this).replace = val;
      return last.call(this) && this;
    }
  };

  // where('class').is('foo').insert('bla')

  exports.bind = function (html, data, map) {
    var merge = new Merge();
    return merge.bind(html, data, map);
  };

  exports.Map = Mapper;

}(Plates);

<commit_msg>Adds understanding of nesting, removing some bloat<commit_after>;var Plates = (typeof process !== 'undefined' && typeof process.title !== 'undefined') ? exports : {};

!function(exports) {

  var _toString = Object.prototype.toString,
      isArray = Array.isArray || function (o) {
        return _toString.call(o) === "[object Array]";
      };

  var Merge = function Merge() {};

  Merge.prototype = {

    nest: [],

    tag: new RegExp([
      '<',
      '(/?)', // 2 - is closing
      '([-:\\w]+)', // 3 - name
      '((?:[-\\w]+(?:', '=',
      '(?:\\w+|["|\'](?:.*)["|\']))?)*)', // 4 - attributes
      '(/?)', // 5 - is self-closing
      '>'
    ].join('\\s*')),

    attr: /([\-\w]*)=(?:["\']([\-\.\w\s\/:;&#]*)["\'])/gi,

    hasClass: function(str, className) {
      return str.indexOf(className) > -1;
    },

    iterate: function(html, value, components, tagname, key) {

      var output  = '',
          segment = html.slice(
            html.search(components.input),
            html.lastIndexOf(tagname) + tagname.length + 1
          ),

          data = {};

      // Is it an array?
      if (isArray(value)) {

        // Yes: set the output to the result of iterating through the array
        for (var i = 0, l = value.length; i < l; i++) {

          // If there is a key, then we have a simple object and
          // must construct a simple object to use as the data
          if (key) {
            data[key] = value[i];
          } else {
            data = value[i];
          }

          output += this.bind(segment, data);

        }

        return output;

      }

      // Is it an object?
      else if (typeof value === 'object') {

        // We need to refine the selection now that we know we're dealing with a
        // nested object
        segment = segment.slice(components.input.length, -(tagname.length + 3));
        return output += this.bind(segment, value);

      }

      return value;

    },

    bind: function bind(html, data, map) {

      if (isArray(data)) {
        var output = '';
        for (var i = 0, l = data.length; i<l; i++) {
          output += this.bind(html, data[i], map);
        }
        return output;
      }

      html = html || '';
      data = data || {};

      var that = this;

      var openers = 0,
          components,
          attributes,
          mappings = map && map.mappings,
          intag = false,
          tagname = '',
          isClosing = false,
          isSelfClosing = false,
          matchmode = false,
          createAttribute = map && map.conf && map.conf.create,
          closing,
          tagbody;

      var c,
          buffer = '',
          left;

      for (var i = 0, l = html.length; i < l; i++) {

        c = html[i];

        if (c === '!' && intag && !matchmode) {
          intag = false;
          buffer += html.slice(left, i+1);
        }
        else if (c === '<' && !intag) {
          closing = true;
          intag = true;
          left = i;
        }
        else if (c === '>' && intag) {

          intag = false;
          tagbody = html.slice(left, i+1);

          components = this.tag.exec(tagbody);

          if(!components) { continue; }

          isClosing = components[1];
          tagname = components[2];
          attributes = components[3];
          isSelfClosing = components[4];

          if (matchmode) {

            //
            // and its a closing.
            //
            if (!!isClosing) {

              if (openers <= 0) {
                matchmode = false;
              }
              else {
                --openers;
              }
            }
            //
            // and its not a self-closing tag
            //
            else if (!isSelfClosing) {
              ++openers;
            }
          }

          if (attributes && !isClosing && !matchmode) {

            //
            // if there is a match in progress and
            //
            if (mappings && mappings.length > 0) {

              for (var ii = mappings.length - 1; ii >= 0; ii--) {

                var setAttribute = false
                  , shouldSetAttribute = mappings[ii].re && attributes.match(mappings[ii].re);

                tagbody = tagbody.replace(this.attr, function(str, key, value, a) {

                  var newdata = mappings[ii].dataKey ? data[mappings[ii].dataKey] : data[key];

                  if (shouldSetAttribute && mappings[ii].replace !== key) {
                    return str;
                  }
                  else if(shouldSetAttribute) {
                    setAttribute = true;
                    return key + '="' + (newdata || '') + '"';
                  }
                  else if (!mappings[ii].replace && mappings[ii].attribute === key) {

                    if (mappings[ii].value === value || that.hasClass(value, mappings[ii].value || mappings.conf.where === key)) {

                      var v = data[mappings[ii].dataKey];

                      newdata = tagbody + newdata;

                      if (isArray(v)) {

                        newdata = that.iterate(html, v, components, tagname, value);

                        // If the item is an array, then we need to tell
                        // Plates that we're dealing with nests
                        that.nest.push(tagname);

                      } else if (typeof v === 'object') {
                        newdata = tagbody + that.iterate(html, v, components, tagname, value);
                      }

                      buffer += newdata || '';

                      matchmode = true;

                    }
                  }

                  return str;

                });

                if (createAttribute && shouldSetAttribute && !setAttribute) {
                  var spliced = isSelfClosing? 2 : 1;
                  var close = isSelfClosing? '/>': '>';
                  var left = tagbody.substr(0, tagbody.length - spliced);
                  if (left[left.length - 1] == ' ') {
                    left = left.substr(0, left.length - 1);
                    if (isSelfClosing) {
                      close = ' ' + close;
                    }
                  }
                  tagbody = [
                    left,
                    ' ',
                    mappings[ii].replace,
                    '="',
                    data[mappings[ii].dataKey],
                    '"',
                    close
                  ].join('');
                }
              }
            }
            else {

              //
              // if there is no map, we are just looking to match
              // the specified id to a data key in the data object.
              //
              tagbody.replace(
                this.attr,
                function (attr, key, value, idx) {
                  if (key === map && map.conf.where || 'id' && data[value]) {

                    var v      = data[value],
                        nest = isArray(v),
                        output = (nest || typeof v === 'object') ? that.iterate(html, v, components, tagname, value) : v;

                    // If the item is an array, then we need to tell
                    // Plates that we're dealing with nests
                    if (nest) { that.nest.push(tagname); }

                    buffer += nest? output : tagbody + output;
                    matchmode = true;
                  }
                }
              );
            }
          }

          //
          // if there is currently no match in progress
          // just write the tagbody to the buffer.
          //
          if (!matchmode && that.nest.length === 0) {
            buffer += tagbody;
          } else if (!matchmode && that.nest.length) {
              this.nest.pop();
          }

        }
        else if (!intag && !matchmode) {

          //
          // currently not inside a tag and there is no
          // match in progress, we can write the char to
          // the buffer.
          //
          buffer += c;
        }

      }
      return buffer;
    }

  };

  var Mapper = function Mapper(conf) {
    if (!(this instanceof Mapper)) { return new Mapper(conf); }
    this.mappings = [];
    this.conf = conf || {};
  };

  function last(newitem) {
    if (newitem) {
      this.mappings.push({});
    }
    var m = this.mappings[this.mappings.length-1];
    if (m.attribute && m.value && m.dataKey && m.replace) {
      m.re = new RegExp(m.attribute + '=([\'"]?)' + m.value + '\\1');
    } else {
      delete m.re;
    }
    return m;
  }

  Mapper.prototype = {
    use: function(val) {
      last.call(this).dataKey = val;
      return last.call(this) && this;
    },
    to: function(val) {
      return this.use(val);
    },
    where: function(val) {
      last.call(this, true).attribute = val;
      return last.call(this) && this;
    },
    class: function(val) {
      return this.where('class').is(val);
    },
    is: function(val) {
      last.call(this).value = val;
      return last.call(this) && this;
    },
    insert: function(val) {
      var l = last.call(this);
      l.replace = l.attribute;
      l.dataKey = val;
      return last.call(this) && this;
    },
    as: function(val) {
      last.call(this).replace = val;
      return last.call(this) && this;
    }
  };

  // where('class').is('foo').insert('bla')

  exports.bind = function (html, data, map) {
    var merge = new Merge();
    return merge.bind(html, data, map);
  };

  exports.Map = Mapper;

}(Plates);<|endoftext|>


<commit_before>// Purely random method, implement structure for other strategies later
// Allocatation strategy should control player, interface, and task
function allocateTask(state){
	var pid = randomItem(state.players);
	var itf = randomItem(Object.keys(state.panels[getPlayerIndex(state, pid)]));
	dispatch({
		action: 'start_task',
		pid: pid,
		itf: itf
	});
}

// Render functions should not mutate the state! They may dispatch events, but do not have to. This allows for non-playing clients that can still monitor the game state tree.

var RENDERERS = {
	// Inputs: pid
	add_player: (state, data) => {
		console.log('Players in the game: ', state.players.join(', '));
		var playersPanel = document.getElementById('players');
			playersPanel.innerHTML += data.pid + ' joined the mission.
';
	},
	// Inputs: void
	setup_controls: (state, data) => {
		// Dirty fix for new team formation
		db.ref('join/' + MISSION_ID).remove();
		// Deal with redundancy later
		dispatch({
			action: 'global_settings',
			win: GLOBAL.WIN,
			lose: GLOBAL.LOSE,
			limit: GLOBAL.LIMIT
		});
		document.querySelector('#welcome').style.display = 'none';
		var promises = [];
		var panelMap = state.panels[getPlayerIndex(state, PLAYER_ID)];
		var itfs = Object.keys(panelMap);
		for(var i = 0; i < itfs.length; i++){
			var interface = panelMap[itfs[i]];
			var p = ControlModule(interface.dom, itfs[i]).then((model) => {
				LOCAL_CONTROL_MODULES[model.itf] = model;
			});
			promises.push(p);
		}
		Promise.all(promises).then((done) => {
			dispatch({
				action: 'start_mission'
			});
		});
	},
	// Inputs: win, lose, limit
	global_settings: (state, data) => {

	},
	// Inputs: void
	start_mission: (state, data) => {
		if(state.playing){
			console.log('Start mission!');
			var adjacentPidx = (state.players.indexOf(PLAYER_ID) + 1) % state.players.length;
			var possibleItfs = state.panels[adjacentPidx];
			var itf = randomItem(Object.keys(possibleItfs));
			dispatch({
				action: 'start_task',
				pid: state.players[adjacentPidx],
				itf: itf
			});
		}
		else{
			console.log(state.checkedIn + ' players are ready.');
		}
	},
	// Inputs: pid, itf
	start_task: (state, data) => {
		var pidx = getPlayerIndex(state, data.pid);
		var interface = state.panels[pidx][data.itf];
		var tid = lastItem(interface.tasks);
		if(data.pid === PLAYER_ID){
			console.log('Activated interface: ', data.itf, ' with task: ', tid);
			var model = LOCAL_CONTROL_MODULES[data.itf];
			model.startTask(tid);
			dispatch({
				action: 'start_instruction',
				pid: data.sender,
				tid: tid
			});
		}
	},
	// Inputs: pid, tid
	start_instruction: (state, data) => {
		if(data.pid === PLAYER_ID){
			console.log('Displayed instructions for task: ', data.tid);
			var task = TASKS[data.tid];
			Terminal.print(task.instruction);
			Terminal.startTimer(data.tid);
		}
	},
	// Inputs: itf, tid
	complete_task: (state, data) => {
		console.log('Completed task: ', data.tid, 'Score: ', state.score.completed.length + '-' + state.score.failed.length);
		reduceObscurity();
		if(state.score.completed.length > state.global.win){
			if(data.sender === PLAYER_ID){
				dispatch({
					action: 'finish_mission'
				});
			}
		}
		var isSender = isTaskSender(state, data);
		if(isSender){
			allocateTask(state); // Only the sender should allocate a new task
		}
	},
	// Inputs: tid
	fail_task: (state, data) => {
		console.log('Failed task: ', data.tid, 'Score: ', state.score.completed.length + '-' + state.score.failed.length);
		if(state.score.failed.length > state.global.lose){
			if(data.sender === PLAYER_ID){
				dispatch({
					action: 'finish_mission'
				});
			}
		}
		var isSender = isTaskSender(state, data);
		if(isSender){
			allocateTask(state); // Only the sender should allocate a new task
		}
		if(data.sender === PLAYER_ID || isSender){
			var mtks = state.score.failed.length;
			if(navigator.vibrate){
				var vlist = [];
				for(var n = 0; n < mtks; n++){
					vlist.push(500);
					vlist.push(250);
				}
				navigator.vibrate(vlist);
			}
			var w = 0;
			var flish = setInterval(function(){
					document.body.style.background = 'red';
				setTimeout(function(){
					document.body.style.background = 'transparent';
				}, 500);
				w++;
				if(w === mtks){
					clearInterval(flish);
				}
			}, 750);
		}
	},
	// Inputs: void
	finish_mission: (state, data) => {
		document.querySelector('#outro').style.display = 'inline-block';
		var result = (state.success) ? 'Success!' : 'Failure';
		console.log('Mission finished, result: ', result, state.score.completed.length + '-' + state.score.failed.length);
		if(state.score.completed.length > state.global.win){
			clearCanvas();
		}
		missionRef.off();
		if(getPlayerIndex(state, PLAYER_ID) === 0){
			archiveMission();
		}
	},
	// Inputs: void
	retry: (state, data) => {
		// Need to turn missionRef back on! to allow this
		LOCAL_CONTROL_MODULES = {};
		joinMission();
		document.querySelector('#outro').style.display = 'inline-block';
	}
}<commit_msg>Update display copy for joining mission control<commit_after>// Purely random method, implement structure for other strategies later
// Allocatation strategy should control player, interface, and task
function allocateTask(state){
	var pid = randomItem(state.players);
	var itf = randomItem(Object.keys(state.panels[getPlayerIndex(state, pid)]));
	dispatch({
		action: 'start_task',
		pid: pid,
		itf: itf
	});
}

// Render functions should not mutate the state! They may dispatch events, but do not have to. This allows for non-playing clients that can still monitor the game state tree.

var RENDERERS = {
	// Inputs: pid
	add_player: (state, data) => {
		console.log('Players in the game: ', state.players.join(', '));
		var playersPanel = document.getElementById('players');
			playersPanel.innerHTML += data.pid + ' connected to mission control.
';
	},
	// Inputs: void
	setup_controls: (state, data) => {
		// Dirty fix for new team formation
		db.ref('join/' + MISSION_ID).remove();
		// Deal with redundancy later
		dispatch({
			action: 'global_settings',
			win: GLOBAL.WIN,
			lose: GLOBAL.LOSE,
			limit: GLOBAL.LIMIT
		});
		document.querySelector('#welcome').style.display = 'none';
		var promises = [];
		var panelMap = state.panels[getPlayerIndex(state, PLAYER_ID)];
		var itfs = Object.keys(panelMap);
		for(var i = 0; i < itfs.length; i++){
			var interface = panelMap[itfs[i]];
			var p = ControlModule(interface.dom, itfs[i]).then((model) => {
				LOCAL_CONTROL_MODULES[model.itf] = model;
			});
			promises.push(p);
		}
		Promise.all(promises).then((done) => {
			dispatch({
				action: 'start_mission'
			});
		});
	},
	// Inputs: win, lose, limit
	global_settings: (state, data) => {

	},
	// Inputs: void
	start_mission: (state, data) => {
		if(state.playing){
			console.log('Start mission!');
			var adjacentPidx = (state.players.indexOf(PLAYER_ID) + 1) % state.players.length;
			var possibleItfs = state.panels[adjacentPidx];
			var itf = randomItem(Object.keys(possibleItfs));
			dispatch({
				action: 'start_task',
				pid: state.players[adjacentPidx],
				itf: itf
			});
		}
		else{
			console.log(state.checkedIn + ' players are ready.');
		}
	},
	// Inputs: pid, itf
	start_task: (state, data) => {
		var pidx = getPlayerIndex(state, data.pid);
		var interface = state.panels[pidx][data.itf];
		var tid = lastItem(interface.tasks);
		if(data.pid === PLAYER_ID){
			console.log('Activated interface: ', data.itf, ' with task: ', tid);
			var model = LOCAL_CONTROL_MODULES[data.itf];
			model.startTask(tid);
			dispatch({
				action: 'start_instruction',
				pid: data.sender,
				tid: tid
			});
		}
	},
	// Inputs: pid, tid
	start_instruction: (state, data) => {
		if(data.pid === PLAYER_ID){
			console.log('Displayed instructions for task: ', data.tid);
			var task = TASKS[data.tid];
			Terminal.print(task.instruction);
			Terminal.startTimer(data.tid);
		}
	},
	// Inputs: itf, tid
	complete_task: (state, data) => {
		console.log('Completed task: ', data.tid, 'Score: ', state.score.completed.length + '-' + state.score.failed.length);
		reduceObscurity();
		if(state.score.completed.length > state.global.win){
			if(data.sender === PLAYER_ID){
				dispatch({
					action: 'finish_mission'
				});
			}
		}
		var isSender = isTaskSender(state, data);
		if(isSender){
			allocateTask(state); // Only the sender should allocate a new task
		}
	},
	// Inputs: tid
	fail_task: (state, data) => {
		console.log('Failed task: ', data.tid, 'Score: ', state.score.completed.length + '-' + state.score.failed.length);
		if(state.score.failed.length > state.global.lose){
			if(data.sender === PLAYER_ID){
				dispatch({
					action: 'finish_mission'
				});
			}
		}
		var isSender = isTaskSender(state, data);
		if(isSender){
			allocateTask(state); // Only the sender should allocate a new task
		}
		if(data.sender === PLAYER_ID || isSender){
			var mtks = state.score.failed.length;
			if(navigator.vibrate){
				var vlist = [];
				for(var n = 0; n < mtks; n++){
					vlist.push(500);
					vlist.push(250);
				}
				navigator.vibrate(vlist);
			}
			var w = 0;
			var flish = setInterval(function(){
					document.body.style.background = 'red';
				setTimeout(function(){
					document.body.style.background = 'transparent';
				}, 500);
				w++;
				if(w === mtks){
					clearInterval(flish);
				}
			}, 750);
		}
	},
	// Inputs: void
	finish_mission: (state, data) => {
		document.querySelector('#outro').style.display = 'inline-block';
		var result = (state.success) ? 'Success!' : 'Failure';
		console.log('Mission finished, result: ', result, state.score.completed.length + '-' + state.score.failed.length);
		if(state.score.completed.length > state.global.win){
			clearCanvas();
		}
		missionRef.off();
		if(getPlayerIndex(state, PLAYER_ID) === 0){
			archiveMission();
		}
	},
	// Inputs: void
	retry: (state, data) => {
		// Need to turn missionRef back on! to allow this
		LOCAL_CONTROL_MODULES = {};
		joinMission();
		document.querySelector('#outro').style.display = 'inline-block';
	}
}<|endoftext|>


<commit_before>var _ = require("underscore")
  , async = require("async")
  , Status = require("./status")

/**
 * The schema for an embedded friendship.  (Treated as an object for compat
 * with embedding schemas)
 */
var Friendship = {
  // the status of this friendship, see the Status module for details
  status: {type: String, enum: _.values(Status)},

  // when this friendship was added
  added: Date
}

/**
 * Default option values
 */
var optionDefaults = {
  /**
   * The name of the document array "friends" path (default: "friends")
   */
  pathName: "friends",

  /**
   * Should an index be added ot the friends doc array? (default: true)
   */
  index: true
};

/**
 * Expose the plugin function as exports
 *
 * Options
 * ---
 * - {String} pathName the pathname for the friends document array (default "friends")
 * - {Boolean} index whether the plugin should automatically index the friends array (default: true)
 *
 * @param {Object} options
 */
module.exports = plugin;

/**
 * Expose Status as a property of the plugin
 */
module.exports.Status = Status;

function plugin (options) {
  options = _.defaults(options || {}, optionDefaults);

  // the pathName for the friends array
  var pathName = options.pathName;

  // Fields to add to the extending model
  var fields = {};
  fields[pathName] = { type: [Friendship], select: false };

  /**
   * The work function which pushes or updates embedded friend objects
   * for two documents, returns a function
   *
   * @api private
   */
  var _update = function (query, update, fship) {
    var options = { new: false };

    return function (done) {
      this.findOneAndUpdate(query, update, options, function (err, res) {
        done(err, fship);
      });
    }
  }

  /**
   * Return a function to update a friendship between two parties
   *
   * @api private
   */
  var updateFriendship = function (m1, m2, fship) {
    var query = {_id: m1};
    query[pathName] = {$elemMatch: {_id: m2}};

    var updater = {$set: {}};
    updater.$set[pathName+".$.status"] = fship.status;

    return _update(query, updater, fship);
  }

  /**
   * Return a function to create a new friendship between two parties
   *
   * @api private
   */
  var pushFriendship = function (m1, m2, fship) {
    var query = {_id: m1};

    fship.added = new Date();

    var updater = {$push: {}};
    updater.$push[pathName] = fship;

    return _update(query, updater, fship);
  }

  /**
   * Generate a function to return one side of a friendship between
   * two models
   *
   * @param Model the extending model
   * @param m1 the model or model _id being queried
   * @param m2 the model or model _id whose friendship is queried for
   *
   * @returns a function which will call back with an friendship for m2 on m1
   * @api private
   */
  var friendshipBetween = function (m1, m2) {
    var proj = {};
    proj[pathName] = {$elemMatch: {_id: m2}};

    return function (done) {
      this.findById(m1, proj, function (err, doc) {
        if (err) return done(err);
        if (!doc) return done("friendship not found");
        done(null, doc[pathName][0]);
      });
    }
  }

  return function friends (schema) {
    // add the embedded friends
    schema.add(fields);

    // Index the friends array with a multikey index on _id.  Further indexing
    // on status is probably unnecessary, as all queries will hit _id and
    // this will already limit them to the friends on an individual user.
    if (options.index) {
      var index = {};
      index[pathName+"._id"] = 1;
      schema.index(index, {name: "friendsplugin"});
    }

    /**
     * Send friend request from "friender" to "friend".  Calls back with
     * an object containing the two resulting friend objects
     *
     * On first request, will result in a "requested" friendship for the
     * first party and a "pending" friendship for the second.
     *
     * Reciprocating the request (friendee back to friender) will accept it.
     *
     * @param {Model} m1 the "friender" model doc or _id doing the reqesting
     * @param {Model} m2 the "friendee" model doc or _id being requested
     * @param {Function} cb callback to execute on completion or error
     */
    schema.statics.requestFriend = function (m1, m2, cb) {
      var steps = {}
        , Model = this

      m1 = m1._id || m1;
      m2 = m2._id || m2;

      cb || (cb = noop);

      async.auto({
        m1: friendshipBetween(m1, m2).bind(this),
        m2: friendshipBetween(m2, m1).bind(this)
      }, function (err, o) {
        if (err) return cb(err);

        var hasfship = !!o.m1
          , fship = o.m1 || {_id: m2}
          , oid = fship._id
          , ostatus = fship.status

        // m2 has no friendship, add a new pending friendship, mark
        // m1's status as requested
        if (!o.m2) {
          fship.status = Status.Requested;
          steps.friend = pushFriendship(m2, m1, {
            _id: m1,
            status: Status.Pending
          }).bind(Model);
        } else {
          switch (o.m2.status) {

          // m2 status is still pending, no update
          case Status.Pending:
            fship.status = Status.Requested;
            break;

          // m2 status is accepted already, no update
          case Status.Accepted:
            fship.status = Status.Accepted;
            break;

          // m2 already requested m1, mark BOTH friendships as accepted
          case Status.Requested:
            fship.status = Status.Accepted;
            steps.friend = updateFriendship(m2, m1, {
              status: Status.Accepted
            }).bind(Model);
            break;
          }
        }

        // If no update was necessary, send the remote friendship back directly
        steps.friend || (steps.friend = function (done) {
          done(null, o.m2)
        });

        // If no update was necessary, send the local friendship back directly
        if (hasfship && ostatus == fship.status && oid.equals(fship._id)) {
          steps.friender = function (done) {
            done(null, fship)
          }
        }
        // Otherwise update it
        else if (hasfship) {
          steps.friender = updateFriendship(m1, m2, fship, cb).bind(Model)
        }
        // Or push a new one if it did not exist prior
        else {
          steps.friender = pushFriendship(m1, m2, fship, cb).bind(Model)
        }

        async.parallel(steps, cb);
      });
    };

    /**
     * Create a friend request
     *
     * @param {Model} friend The potential friend being requested
     * @param {Function} cb Callback once friendship is created
     */
    schema.methods.requestFriend = function (friend, cb) {
      return this.constructor.requestFriend(this, friend, cb);
    };

    /**
     * Get all friends of a model
     *
     * @param {Model|ObjectId|HexId} model doc or _id to query friends of
     * @param {Object} conditions
     * @param {Object} [fields] optional fields to select
     * @param {Object} [options] optional
     * @param {Function} [callback]
     * @see Mongoose Model.find
     */
    schema.statics.getFriends = function (model) {
      var Model = this
        , op = 'find'

      resolveArgs(arguments, function (conditions, fields, options, cb) {
        // assume model is either a Model, or an _id
        model = model._id || model;

        conditions[pathName+"._id"] = model;

        // "accepted" status is mirrored on both sides, but if querying
        // for "pending" or "requested" friends, the query must be reversed
        if (conditions[pathName+".status"]) {
          switch (conditions[pathName+".status"]) {
          case Status.Pending:
            conditions[pathName+".status"] = Status.Requested;
            break;
          case Status.Requested:
            conditions[pathName+".status"] = Status.Pending;
            break;
          }
        }

        async.parallel({
          // query remote friends based on the arguments
          friends: function (done) {
            Model[op](conditions, fields, options, done);
          },

          // Reduce local friend docs to map which will be populated and
          // then combined with the queried remote friends to generate the
          // resulting array
          locals: function (done) {
            var select = {};
            select[pathName] = 1;

            Model.findOne({_id: model}, select, function (err, doc) {
              if (err) return done(err);
              if (!doc) return done(null, []);

              done(null, _.reduce(doc[pathName], function (o, friend) {
                o[friend._id] = friend.toObject();
                return o;
              }, {}));
            });
          }
        }, function (err, res) {
          if (err) return cb(err);

          // wrap the results with the status
          for (var i=0, friendship; i<res.friends.length; i++) {
            friendship = res.locals[res.friends[i]._id];
            friendship.friend = res.friends[i];
            res.friends[i] = friendship;
          }

          cb(null, res.friends);
        });
      });
    };

    /**
     * Get all friends of this model
     *
     * @param {Object} conditions
     * @param {Object} [fields] optional fields to select
     * @param {Object} [options] optional
     * @param {Function} [callback]
     * @see Mongoose Model.find
     */
    schema.methods.getFriends = function (conditions, fields, options, cb) {
      return this.constructor.getFriends(this, conditions, fields, options, cb);
    };

    var getByStatus = function (status) {
      return function (model) {
        resolveArgs(arguments, function (conditions, fields, options, cb) {
          conditions[pathName + ".status"] = status;
          this.getFriends(model, conditions, fields, options, cb);
        }.bind(this));
      }
    }

    /**
     * Get all pending friends for a given model
     */
    schema.statics.getPendingFriends = getByStatus(Status.Pending);

    /**
     * Get all pending friends for this model
     */
    schema.methods.getPendingFriends = function (conditions, fields, options, cb) {
      return this.constructor.getPendingFriends(this, conditions, fields, options, cb);
    }

    /**
     * Get all accepted friends for a given model
     */
    schema.statics.getAcceptedFriends = getByStatus(Status.Accepted);

    /**
     * Get all accepted friends for this model
     */
    schema.methods.getAcceptedFriends = function (conditions, fields, options, cb) {
      return this.constructor.getAcceptedFriends(this, conditions, fields, options, cb);
    }

    /**
     * Get all requested friends for a given model
     */
    schema.statics.getRequestedFriends = getByStatus(Status.Requested);

    /**
     * Get all requested friends for this model
     */
    schema.methods.getRequestedFriends = function (conditions, fields, options, cb) {
      return this.constructor.getRequestedFriends(this, conditions, fields, options, cb);
    }

    /**
     * Remove a friendship between two friends
     *
     * @param {Model} m1 the first model doc
     * @param {Model} m2 the second model doc
     * @param {Function} cb callback to execute on completion or error
     */
    schema.statics.removeFriend = function (m1, m2, cb) {
      var collection = this.collection

      m1 = m1._id || m1;
      m2 = m2._id || m2;

      cb || (cb = noop);

      var pull = function (m1, m2) {
        var update = {$pull: {}}
        update.$pull[pathName] = {_id: m2};
        return function (done) {
          collection.update({_id: m1}, update, done);
        }
      }

      async.parallel([pull(m1, m2), pull(m2, m1)], cb);
    };

    /**
     * Remove a friend of this model
     *
     * @param {Model} friend doc to remove
     * @param {Function} cb callback to execute on completion or error
     */
    schema.methods.removeFriend = function (model, cb) {
      return this.constructor.removeFriend(this, model, cb);
    };
  };
}

/**
 * Expects a signature like Mongoose's `Model.find`, but for the first
 * arg.  Goes through the argument same disambigation that `Model.find`
 * does then calls a callback with the results.  This keeps all this
 * business in one place and lets other functions hook into the options
 * of `getFriends` without copy/pasting this Mongoose logic.
 *
 * @api private
 */
function resolveArgs (args, next) {
  var conditions = args[1]
    , fields = args[2]
    , options = args[3]
    , callback = args[4]

  if ('function' == typeof conditions) {
    callback = conditions;
    conditions = {};
    fields = null;
    options = null;
  } else if ('function' == typeof fields) {
    callback = fields;
    fields = null;
    options = null;
  } else if ('function' == typeof options) {
    callback = options;
    options = null;
  }
  next(conditions, fields, options, callback);
}

/**
 * simple no op function for clarity
 * @api private
 */
function noop() {}
<commit_msg>Fix status methods, use `$elemMatch` to match all conditions.<commit_after>var _ = require("underscore")
  , async = require("async")
  , Status = require("./status")

/**
 * The schema for an embedded friendship.  (Treated as an object for compat
 * with embedding schemas)
 */
var Friendship = {
  // the status of this friendship, see the Status module for details
  status: {type: String, enum: _.values(Status)},

  // when this friendship was added
  added: Date
}

/**
 * Default option values
 */
var optionDefaults = {
  /**
   * The name of the document array "friends" path (default: "friends")
   */
  pathName: "friends",

  /**
   * Should an index be added ot the friends doc array? (default: true)
   */
  index: true
};

/**
 * Expose the plugin function as exports
 *
 * Options
 * ---
 * - {String} pathName the pathname for the friends document array (default "friends")
 * - {Boolean} index whether the plugin should automatically index the friends array (default: true)
 *
 * @param {Object} options
 */
module.exports = plugin;

/**
 * Expose Status as a property of the plugin
 */
module.exports.Status = Status;

function plugin (options) {
  options = _.defaults(options || {}, optionDefaults);

  // the pathName for the friends array
  var pathName = options.pathName;

  // Fields to add to the extending model
  var fields = {};
  fields[pathName] = { type: [Friendship], select: false };

  /**
   * The work function which pushes or updates embedded friend objects
   * for two documents, returns a function
   *
   * @api private
   */
  var _update = function (query, update, fship) {
    var options = { new: false };

    return function (done) {
      this.findOneAndUpdate(query, update, options, function (err, res) {
        done(err, fship);
      });
    }
  }

  /**
   * Return a function to update a friendship between two parties
   *
   * @api private
   */
  var updateFriendship = function (m1, m2, fship) {
    var query = {_id: m1};
    query[pathName] = {$elemMatch: {_id: m2}};

    var updater = {$set: {}};
    updater.$set[pathName+".$.status"] = fship.status;

    return _update(query, updater, fship);
  }

  /**
   * Return a function to create a new friendship between two parties
   *
   * @api private
   */
  var pushFriendship = function (m1, m2, fship) {
    var query = {_id: m1};

    fship.added = new Date();

    var updater = {$push: {}};
    updater.$push[pathName] = fship;

    return _update(query, updater, fship);
  }

  /**
   * Generate a function to return one side of a friendship between
   * two models
   *
   * @param Model the extending model
   * @param m1 the model or model _id being queried
   * @param m2 the model or model _id whose friendship is queried for
   *
   * @returns a function which will call back with an friendship for m2 on m1
   * @api private
   */
  var friendshipBetween = function (m1, m2) {
    var proj = {};
    proj[pathName] = {$elemMatch: {_id: m2}};

    return function (done) {
      this.findById(m1, proj, function (err, doc) {
        if (err) return done(err);
        if (!doc) return done("friendship not found");
        done(null, doc[pathName][0]);
      });
    }
  }

  return function friends (schema) {
    // add the embedded friends
    schema.add(fields);

    // Index the friends array with a multikey index on _id.  Further indexing
    // on status is probably unnecessary, as all queries will hit _id and
    // this will already limit them to the friends on an individual user.
    if (options.index) {
      var index = {};
      index[pathName+"._id"] = 1;
      schema.index(index, {name: "friendsplugin"});
    }

    /**
     * Send friend request from "friender" to "friend".  Calls back with
     * an object containing the two resulting friend objects
     *
     * On first request, will result in a "requested" friendship for the
     * first party and a "pending" friendship for the second.
     *
     * Reciprocating the request (friendee back to friender) will accept it.
     *
     * @param {Model} m1 the "friender" model doc or _id doing the reqesting
     * @param {Model} m2 the "friendee" model doc or _id being requested
     * @param {Function} cb callback to execute on completion or error
     */
    schema.statics.requestFriend = function (m1, m2, cb) {
      var steps = {}
        , Model = this

      m1 = m1._id || m1;
      m2 = m2._id || m2;

      cb || (cb = noop);

      async.auto({
        m1: friendshipBetween(m1, m2).bind(this),
        m2: friendshipBetween(m2, m1).bind(this)
      }, function (err, o) {
        if (err) return cb(err);

        var hasfship = !!o.m1
          , fship = o.m1 || {_id: m2}
          , oid = fship._id
          , ostatus = fship.status

        // m2 has no friendship, add a new pending friendship, mark
        // m1's status as requested
        if (!o.m2) {
          fship.status = Status.Requested;
          steps.friend = pushFriendship(m2, m1, {
            _id: m1,
            status: Status.Pending
          }).bind(Model);
        } else {
          switch (o.m2.status) {

          // m2 status is still pending, no update
          case Status.Pending:
            fship.status = Status.Requested;
            break;

          // m2 status is accepted already, no update
          case Status.Accepted:
            fship.status = Status.Accepted;
            break;

          // m2 already requested m1, mark BOTH friendships as accepted
          case Status.Requested:
            fship.status = Status.Accepted;
            steps.friend = updateFriendship(m2, m1, {
              status: Status.Accepted
            }).bind(Model);
            break;
          }
        }

        // If no update was necessary, send the remote friendship back directly
        steps.friend || (steps.friend = function (done) {
          done(null, o.m2)
        });

        // If no update was necessary, send the local friendship back directly
        if (hasfship && ostatus == fship.status && oid.equals(fship._id)) {
          steps.friender = function (done) {
            done(null, fship)
          }
        }
        // Otherwise update it
        else if (hasfship) {
          steps.friender = updateFriendship(m1, m2, fship, cb).bind(Model)
        }
        // Or push a new one if it did not exist prior
        else {
          steps.friender = pushFriendship(m1, m2, fship, cb).bind(Model)
        }

        async.parallel(steps, cb);
      });
    };

    /**
     * Create a friend request
     *
     * @param {Model} friend The potential friend being requested
     * @param {Function} cb Callback once friendship is created
     */
    schema.methods.requestFriend = function (friend, cb) {
      return this.constructor.requestFriend(this, friend, cb);
    };

    /**
     * Get all friends of a model
     *
     * @param {Model|ObjectId|HexId} model doc or _id to query friends of
     * @param {Object} conditions
     * @param {Object} [fields] optional fields to select
     * @param {Object} [options] optional
     * @param {Function} [callback]
     * @see Mongoose Model.find
     */
    schema.statics.getFriends = function (model) {
      var Model = this
        , op = 'find'

      resolveArgs(arguments, function (conditions, fields, options, cb) {
        // assume model is either a Model, or an _id
        model = model._id || model;

        // conditions for embedded friend docs
        conditions[pathName] = conditions[pathName] || {};
        conditions[pathName]['$elemMatch'] = conditions[pathName]['$elemMatch'] || {};
        conditions[pathName]['$elemMatch']['_id'] = model;

        // "accepted" status is mirrored on both sides, but if querying
        // for "pending" or "requested" friends, the query must be reversed
        if (conditions[pathName]['$elemMatch']['status']) {
          switch (conditions[pathName]['$elemMatch']['status']) {
          case Status.Pending:
            conditions[pathName]['$elemMatch']['status'] = Status.Requested;
            break;
          case Status.Requested:
            conditions[pathName]['$elemMatch']['status'] = Status.Pending;
            break;
          }
        }

        async.parallel({
          // query remote friends based on the arguments
          friends: function (done) {
            Model[op](conditions, fields, options, done);
          },

          // Reduce local friend docs to map which will be populated and
          // then combined with the queried remote friends to generate the
          // resulting array
          locals: function (done) {
            var select = {};
            select[pathName] = 1;

            Model.findOne({_id: model}, select, function (err, doc) {
              if (err) return done(err);
              if (!doc) return done(null, []);

              done(null, _.reduce(doc[pathName], function (o, friend) {
                o[friend._id] = friend.toObject();
                return o;
              }, {}));
            });
          }
        }, function (err, res) {
          if (err) return cb(err);

          // wrap the results with the status
          for (var i=0, friendship; i<res.friends.length; i++) {
            friendship = res.locals[res.friends[i]._id];
            friendship.friend = res.friends[i];
            res.friends[i] = friendship;
          }

          cb(null, res.friends);
        });
      });
    };

    /**
     * Get all friends of this model
     *
     * @param {Object} conditions
     * @param {Object} [fields] optional fields to select
     * @param {Object} [options] optional
     * @param {Function} [callback]
     * @see Mongoose Model.find
     */
    schema.methods.getFriends = function (conditions, fields, options, cb) {
      return this.constructor.getFriends(this, conditions, fields, options, cb);
    };

    var getByStatus = function (status) {
      return function (model) {
        resolveArgs(arguments, function (conditions, fields, options, cb) {
          conditions[pathName] = conditions[pathName] || {};
          conditions[pathName]['$elemMatch'] = conditions[pathName]['$elemMatch'] || {};
          conditions[pathName]['$elemMatch']['status'] = status;

          this.getFriends(model, conditions, fields, options, cb);
        }.bind(this));
      }
    }

    /**
     * Get all pending friends for a given model
     */
    schema.statics.getPendingFriends = getByStatus(Status.Pending);

    /**
     * Get all pending friends for this model
     */
    schema.methods.getPendingFriends = function (conditions, fields, options, cb) {
      return this.constructor.getPendingFriends(this, conditions, fields, options, cb);
    }

    /**
     * Get all accepted friends for a given model
     */
    schema.statics.getAcceptedFriends = getByStatus(Status.Accepted);

    /**
     * Get all accepted friends for this model
     */
    schema.methods.getAcceptedFriends = function (conditions, fields, options, cb) {
      return this.constructor.getAcceptedFriends(this, conditions, fields, options, cb);
    }

    /**
     * Get all requested friends for a given model
     */
    schema.statics.getRequestedFriends = getByStatus(Status.Requested);

    /**
     * Get all requested friends for this model
     */
    schema.methods.getRequestedFriends = function (conditions, fields, options, cb) {
      return this.constructor.getRequestedFriends(this, conditions, fields, options, cb);
    }

    /**
     * Remove a friendship between two friends
     *
     * @param {Model} m1 the first model doc
     * @param {Model} m2 the second model doc
     * @param {Function} cb callback to execute on completion or error
     */
    schema.statics.removeFriend = function (m1, m2, cb) {
      var collection = this.collection

      m1 = m1._id || m1;
      m2 = m2._id || m2;

      cb || (cb = noop);

      var pull = function (m1, m2) {
        var update = {$pull: {}}
        update.$pull[pathName] = {_id: m2};
        return function (done) {
          collection.update({_id: m1}, update, done);
        }
      }

      async.parallel([pull(m1, m2), pull(m2, m1)], cb);
    };

    /**
     * Remove a friend of this model
     *
     * @param {Model} friend doc to remove
     * @param {Function} cb callback to execute on completion or error
     */
    schema.methods.removeFriend = function (model, cb) {
      return this.constructor.removeFriend(this, model, cb);
    };
  };
}

/**
 * Expects a signature like Mongoose's `Model.find`, but for the first
 * arg.  Goes through the argument same disambigation that `Model.find`
 * does then calls a callback with the results.  This keeps all this
 * business in one place and lets other functions hook into the options
 * of `getFriends` without copy/pasting this Mongoose logic.
 *
 * @api private
 */
function resolveArgs (args, next) {
  var conditions = args[1]
    , fields = args[2]
    , options = args[3]
    , callback = args[4]

  if ('function' == typeof conditions) {
    callback = conditions;
    conditions = {};
    fields = null;
    options = null;
  } else if ('function' == typeof fields) {
    callback = fields;
    fields = null;
    options = null;
  } else if ('function' == typeof options) {
    callback = options;
    options = null;
  }
  next(conditions, fields, options, callback);
}

/**
 * simple no op function for clarity
 * @api private
 */
function noop() {}
<|endoftext|>


<commit_before>/**
 * jsonalyzer Python code completion
 *
 * @copyright 2015, Ajax.org B.V.
 * @author Lennart Kats <lennart add c9.io>
 */
define(function(require, exports, module) {

var baseHandler = require("plugins/c9.ide.language/base_handler");
var workerUtil = require("plugins/c9.ide.language/worker_util");

var KEYWORD_REGEX = new RegExp(
    "^(and|as|assert|break|class|continue|def|del|elif|else|except|exec|"
    + "finally|for|from|global|if|import|in|is|lambda|not|or|pass|print|"
    + "raise|return|try|while|with|yield)$"
);
var DAEMON_PORT = 10880;
var ERROR_PORT_IN_USE = 98;
var ERROR_NO_SERVER = 7;

var handler = module.exports = Object.create(baseHandler);
var pythonVersion = "python2";
var jediServer;
var launchCommand;
var showedJediError;
var daemon;

handler.handlesLanguage = function(language) {
    return language === "python";
};

handler.init = function(callback) {
    handler.sender.on("set_python_version", function(e) {
        pythonVersion = e.data;
        if (daemon) {
            daemon.kill();
            daemon = null;
        }
    });
    handler.sender.on("set_python_scripts", function(e) {
        jediServer = e.data.jediServer;
        launchCommand = e.data.launchCommand;
    });
    callback();
};

handler.getCompletionRegex = function() {
    return (/^([\.]|\bimport )$/);
};

/**
 * Complete code at the current cursor position.
 */
handler.complete = function(doc, fullAst, pos, currentNode, callback) {
    callDaemon("completions", handler.path, doc, pos, function(err, results, meta) {
        if (err) return callback(err);

        results && results.forEach(function beautifyCompletion(r) {
            r.isContextual = true;
            r.guessTooltip = true;
            r.priority = r.name[0] === "_" ? 3 : 4;
            r.icon = r.icon || "property";
            r.icon = r.name[0] === "_" ? r.icon.replace(/2?$/, "2") : r.icon;
            if (!r.doc)
                return;
            var docLines = r.doc.split(/\r\n|\n|\r/);
            var docBody = docLines.slice(2).join("\n");
            r.docHeadHtml = workerUtil.filterDocumentation(docLines[0]);
            r.doc = workerUtil.filterDocumentation(docBody.replace(/``/g, "'"));
        });
        callback(null, results);
    });
};

/**
 * Jump to the definition of what's under the cursor.
 */
handler.jumpToDefinition = function(doc, fullAst, pos, currentNode, callback) {
    callDaemon("goto_definitions", handler.path, doc, pos, callback);
};

/**
 * Predict how to complete code next. Did the user just type 'mat'?
 * Then we probably only have a completion 'math'. So we can predict
 * that the user may type 'math.' next and precompute completions.
 */
handler.predictNextCompletion = function(doc, fullAst, pos, options, callback) {
    var predicted = options.matches.filter(function(m) {
        return m.isContextual
            && m.icon !== "method"
            && !m.replaceText.match(KEYWORD_REGEX);
    });
    var line = doc.getLine(pos.row);
    if (predicted.length === 0 && "import".substr(0, line.length) === line)
        return callback("import ");
    if (predicted.length !== 1)
        return callback();
    if (/^\s+import /.test(line))
        return callback();
    console.log("[python_worker] Predicted our next completion will be for " + predicted[0].replaceText + ".");
    callback(null, { predicted: predicted[0].replaceText + "." });
};

/**
 * Invoke a function on our jedi python daemon. It runs as an HTTP daemon
 * so we use curl to send a request.
 */
function callDaemon(command, path, doc, pos, callback) {
    var line = doc.getLine(pos.row);
    ensureDaemon(function(err, dontRetry) {
        if (err) return callback(err);

        var start = Date.now();
        workerUtil.execAnalysis(
            "curl",
            {
                mode: "stdin",
                json: true,
                args: [
                    "-s", "--data-binary", "@-", // get input from stdin
                    "localhost:" + DAEMON_PORT + "?mode=" + command
                    + "&row=" + (pos.row + 1) + "&column=" + pos.column + "&path=" + path,
                ]
            },
            function onResult(err, stdout, stderr, meta) {
                if (err) {
                    if (err.code === ERROR_NO_SERVER && !dontRetry) {
                        daemon = null;
                        return callDaemon(command, path, doc, pos, callback);
                    }
                    return callback(err);
                }

                if (typeof stdout !== "object")
                    return callback(new Error("Couldn't parse python-jedi output: " + stdout));

                console.log("[python_worker] " + command + " in " + (Date.now() - start)
                    + "ms (jedi: " + meta.serverTime + "ms, transferred: " + meta.size + "b): "
                    + line.substr(0, pos.column));

                callback(null, stdout, meta);
            }
        );
    });
}

/**
 * Make sure we're running a jedi daemon (../server/jedi_server.py).
 * It listens on a port in the workspace container or host.
 */
function ensureDaemon(callback) {
    if (daemon)
        return done(daemon.err);

    daemon = {
        err: new Error("Still starting daemon, enhance your calm"),
        kill: function() {
            this.killed = true;
        }
    };

    workerUtil.spawn(
        "bash",
        {
            args: [
                "-c", launchCommand, "--", pythonVersion,
                "$PYTHON -c '" + jediServer + "' daemon --port " + DAEMON_PORT
            ]
        },
        function(err, child) {
            var output = "";
            if (err) {
                daemon.err = err;
                return workerUtil.showError("Could not start python completion daemon. Please reload to try again.");
            }
            daemon = child;
            daemon.err = null;

            if (daemon.killed)
                daemon.kill();

            // We (re)start the daemon after 10 minutes to conserve memory
            var killTimer = setTimeout(daemon.kill.bind(daemon), 10 * 60 * 1000);

            child.stderr.on("data", function(data) {
                output += data;
                if (/Daemon listening/.test(data))
                    done();
            });
            child.on("exit", function(code) {
                if (code === ERROR_PORT_IN_USE) // someone else running daemon?
                    return done(null, true);
                if (!code || /Daemon listening/.test(output)) // everything ok, try again later
                    daemon = null;
                clearTimeout(killTimer);
                done(code && new Error("[python_worker] Daemon failed: " + output));
            });
        }
    );

    function done(err) {
        if (err && /No module named jedi/.test(err.message) && !showedJediError) {
            workerUtil.showError("Jedi not found. Please run 'pip install jedi' or 'sudo pip install jedi' to enable Python code completion.");
            showedJediError = true;
        }
        callback && callback(err);
        callback = null;
    }
}

});<commit_msg>Conservatively show some Python completions early<commit_after>/**
 * jsonalyzer Python code completion
 *
 * @copyright 2015, Ajax.org B.V.
 * @author Lennart Kats <lennart add c9.io>
 */
define(function(require, exports, module) {

var baseHandler = require("plugins/c9.ide.language/base_handler");
var workerUtil = require("plugins/c9.ide.language/worker_util");

var KEYWORD_REGEX = new RegExp(
    "^(and|as|assert|break|class|continue|def|del|elif|else|except|exec|"
    + "finally|for|from|global|if|import|in|is|lambda|not|or|pass|print|"
    + "raise|return|try|while|with|yield)$"
);
var DAEMON_PORT = 10880;
var ERROR_PORT_IN_USE = 98;
var ERROR_NO_SERVER = 7;

var handler = module.exports = Object.create(baseHandler);
var pythonVersion = "python2";
var jediServer;
var launchCommand;
var showedJediError;
var daemon;

handler.handlesLanguage = function(language) {
    return language === "python";
};

handler.init = function(callback) {
    handler.sender.on("set_python_version", function(e) {
        pythonVersion = e.data;
        if (daemon) {
            daemon.kill();
            daemon = null;
        }
    });
    handler.sender.on("set_python_scripts", function(e) {
        jediServer = e.data.jediServer;
        launchCommand = e.data.launchCommand;
    });
    callback();
};

handler.getCompletionRegex = function() {
    return (/^([\.]|\bimport )$/);
};

/**
 * Complete code at the current cursor position.
 */
handler.complete = function(doc, fullAst, pos, currentNode, callback) {
    callDaemon("completions", handler.path, doc, pos, function(err, results, meta) {
        if (err) return callback(err);

        results && results.forEach(function beautifyCompletion(r) {
            r.isContextual = true;
            r.guessTooltip = true;
            r.priority = r.name[0] === "_" ? 3 : 4;
            r.icon = r.icon || "property";
            r.icon = r.name[0] === "_" ? r.icon.replace(/2?$/, "2") : r.icon;
            if (!r.doc)
                return;
            var docLines = r.doc.split(/\r\n|\n|\r/);
            var docBody = docLines.slice(2).join("\n");
            r.docHeadHtml = workerUtil.filterDocumentation(docLines[0]);
            r.doc = workerUtil.filterDocumentation(docBody.replace(/``/g, "'"));
        });
        callback(null, results);
    });
};

/**
 * Jump to the definition of what's under the cursor.
 */
handler.jumpToDefinition = function(doc, fullAst, pos, currentNode, callback) {
    callDaemon("goto_definitions", handler.path, doc, pos, callback);
};

/**
 * Predict how to complete code next. Did the user just type 'mat'?
 * Then we probably only have a completion 'math'. So we can predict
 * that the user may type 'math.' next and precompute completions.
 */
handler.predictNextCompletion = function(doc, fullAst, pos, options, callback) {
    var predicted = options.matches.filter(function(m) {
        return m.isContextual
            && m.icon !== "method"
            && !m.replaceText.match(KEYWORD_REGEX);
    });
    var line = doc.getLine(pos.row);
    if (predicted.length === 0 && "import".substr(0, line.length) === line)
        return callback("import ");
    if (predicted.length !== 1)
        return callback();
    if (/^\s+import /.test(line))
        return callback();
    console.log("[python_worker] Predicted our next completion will be for " + predicted[0].replaceText + ".");
    callback(null, {
        predicted: predicted[0].replaceText + ".",
        showEarly: predicted[0].replaceText === "self"
    });
};

/**
 * Invoke a function on our jedi python daemon. It runs as an HTTP daemon
 * so we use curl to send a request.
 */
function callDaemon(command, path, doc, pos, callback) {
    var line = doc.getLine(pos.row);
    ensureDaemon(function(err, dontRetry) {
        if (err) return callback(err);

        var start = Date.now();
        workerUtil.execAnalysis(
            "curl",
            {
                mode: "stdin",
                json: true,
                args: [
                    "-s", "--data-binary", "@-", // get input from stdin
                    "localhost:" + DAEMON_PORT + "?mode=" + command
                    + "&row=" + (pos.row + 1) + "&column=" + pos.column + "&path=" + path,
                ]
            },
            function onResult(err, stdout, stderr, meta) {
                if (err) {
                    if (err.code === ERROR_NO_SERVER && !dontRetry) {
                        daemon = null;
                        return callDaemon(command, path, doc, pos, callback);
                    }
                    return callback(err);
                }

                if (typeof stdout !== "object")
                    return callback(new Error("Couldn't parse python-jedi output: " + stdout));

                console.log("[python_worker] " + command + " in " + (Date.now() - start)
                    + "ms (jedi: " + meta.serverTime + "ms, transferred: " + meta.size + "b): "
                    + line.substr(0, pos.column));

                callback(null, stdout, meta);
            }
        );
    });
}

/**
 * Make sure we're running a jedi daemon (../server/jedi_server.py).
 * It listens on a port in the workspace container or host.
 */
function ensureDaemon(callback) {
    if (daemon)
        return done(daemon.err);

    daemon = {
        err: new Error("Still starting daemon, enhance your calm"),
        kill: function() {
            this.killed = true;
        }
    };

    workerUtil.spawn(
        "bash",
        {
            args: [
                "-c", launchCommand, "--", pythonVersion,
                "$PYTHON -c '" + jediServer + "' daemon --port " + DAEMON_PORT
            ]
        },
        function(err, child) {
            var output = "";
            if (err) {
                daemon.err = err;
                return workerUtil.showError("Could not start python completion daemon. Please reload to try again.");
            }
            daemon = child;
            daemon.err = null;

            if (daemon.killed)
                daemon.kill();

            // We (re)start the daemon after 10 minutes to conserve memory
            var killTimer = setTimeout(daemon.kill.bind(daemon), 10 * 60 * 1000);

            child.stderr.on("data", function(data) {
                output += data;
                if (/Daemon listening/.test(data))
                    done();
            });
            child.on("exit", function(code) {
                if (code === ERROR_PORT_IN_USE) // someone else running daemon?
                    return done(null, true);
                if (!code || /Daemon listening/.test(output)) // everything ok, try again later
                    daemon = null;
                clearTimeout(killTimer);
                done(code && new Error("[python_worker] Daemon failed: " + output));
            });
        }
    );

    function done(err) {
        if (err && /No module named jedi/.test(err.message) && !showedJediError) {
            workerUtil.showError("Jedi not found. Please run 'pip install jedi' or 'sudo pip install jedi' to enable Python code completion.");
            showedJediError = true;
        }
        callback && callback(err);
        callback = null;
    }
}

});<|endoftext|>


<commit_before>/* eslint-disable import/no-extraneous-dependencies */
const Promise = require('bluebird');
const Chunk = require('webpack/lib/Chunk');
const SVGCompiler = require('svg-baker');
const Sprite = require('svg-baker/lib/sprite');
const { NAMESPACE } = require('./config');
const { MappedList, replaceInModuleSource, replaceSpritePlaceholder } = require('./utils');

class SVGSpritePlugin {
  constructor() {
    this.svgCompiler = new SVGCompiler();
  }

  /**
   * This need to find plugin from loader context
   */
  // eslint-disable-next-line class-methods-use-this
  get NAMESPACE() {
    return NAMESPACE;
  }

  // TODO optimize MappedList instantiation in each hook
  apply(compiler) {
    const plugin = this;
    const { symbols } = this.svgCompiler;

    // Handle only main compilation
    compiler.plugin('this-compilation', (compilation) => {
      // Share svgCompiler with loader
      compilation.plugin('normal-module-loader', (loaderContext) => {
        loaderContext[NAMESPACE] = plugin;
      });

      // Replace placeholders with real URL to symbol (in modules processed by sprite-loader)
      compilation.plugin('after-optimize-chunks', function replacePlaceholdersInModules() {
        const map = new MappedList(symbols, this);
        const replacements = map.groupItemsBySymbolFile((acc, item) => acc[item.resource] = item.useUrl);
        map.items.forEach(item => replaceInModuleSource(item.module, replacements));
      });

      // Hook into extract-text-webpack-plugin event to replace placeholders with real URL to symbol
      compilation.plugin('optimize-extracted-chunks', function replacePlaceholdersInExtractedChunks(chunks) {
        const map = new MappedList(symbols, this);
        const replacements = map.groupItemsBySymbolFile((acc, item) => acc[item.resource] = item.useUrl);

        chunks.forEach((chunk) => {
          chunk.modules
            // dirty hack to identify modules extracted by extract-text-webpack-plugin
            // TODO refactor
            .filter(module => '_originalModule' in module)
            .forEach(module => replaceInModuleSource(module, replacements));
        });
      });

      // Hook into html-webpack-plugin event to replace placeholders with real URL to symbol
      compilation.plugin('html-webpack-plugin-before-html-processing', function htmlPluginHook(htmlPluginData, done) {
        const map = new MappedList(symbols, this);
        const replacements = map.groupItemsBySymbolFile((acc, item) => acc[item.resource] = item.useUrl);
        htmlPluginData.html = replaceSpritePlaceholder(htmlPluginData.html, replacements);
        done(null, htmlPluginData);
      });

      // Create sprite chunk
      compilation.plugin('additional-assets', function emitSpriteChunks(done) {
        const map = new MappedList(symbols, this);
        const itemsBySprite = map.groupItemsBySpriteFilename();
        const filenames = Object.keys(itemsBySprite);

        return Promise.map(filenames, (filename) => {
          const spriteSymbols = itemsBySprite[filename].map(item => item.symbol);

          return Sprite.create({ symbols: spriteSymbols })
            .then((sprite) => {
              const content = sprite.render();
              const chunk = new Chunk(filename);
              chunk.ids = [];
              chunk.files.push(filename);

              compilation.assets[filename] = {
                source() { return content; },
                size() { return content.length; }
              };

              compilation.chunks.push(chunk);
            });
        }).then(() => {
          done();
          return true;
        }).catch(e => done(e));
      });
    });
  }
}

module.exports = SVGSpritePlugin;
<commit_msg>fix(plugin): webpack-manifest-plugin compatibility<commit_after>/* eslint-disable import/no-extraneous-dependencies */
const Promise = require('bluebird');
const Chunk = require('webpack/lib/Chunk');
const SVGCompiler = require('svg-baker');
const Sprite = require('svg-baker/lib/sprite');
const { NAMESPACE } = require('./config');
const { MappedList, replaceInModuleSource, replaceSpritePlaceholder } = require('./utils');

class SVGSpritePlugin {
  constructor() {
    this.svgCompiler = new SVGCompiler();
  }

  /**
   * This need to find plugin from loader context
   */
  // eslint-disable-next-line class-methods-use-this
  get NAMESPACE() {
    return NAMESPACE;
  }

  // TODO optimize MappedList instantiation in each hook
  apply(compiler) {
    const plugin = this;
    const { symbols } = this.svgCompiler;

    // Handle only main compilation
    compiler.plugin('this-compilation', (compilation) => {
      // Share svgCompiler with loader
      compilation.plugin('normal-module-loader', (loaderContext) => {
        loaderContext[NAMESPACE] = plugin;
      });

      // Replace placeholders with real URL to symbol (in modules processed by sprite-loader)
      compilation.plugin('after-optimize-chunks', function replacePlaceholdersInModules() {
        const map = new MappedList(symbols, this);
        const replacements = map.groupItemsBySymbolFile((acc, item) => acc[item.resource] = item.useUrl);
        map.items.forEach(item => replaceInModuleSource(item.module, replacements));
      });

      // Hook into extract-text-webpack-plugin event to replace placeholders with real URL to symbol
      compilation.plugin('optimize-extracted-chunks', function replacePlaceholdersInExtractedChunks(chunks) {
        const map = new MappedList(symbols, this);
        const replacements = map.groupItemsBySymbolFile((acc, item) => acc[item.resource] = item.useUrl);

        chunks.forEach((chunk) => {
          chunk.modules
            // dirty hack to identify modules extracted by extract-text-webpack-plugin
            // TODO refactor
            .filter(module => '_originalModule' in module)
            .forEach(module => replaceInModuleSource(module, replacements));
        });
      });

      // Hook into html-webpack-plugin event to replace placeholders with real URL to symbol
      compilation.plugin('html-webpack-plugin-before-html-processing', function htmlPluginHook(htmlPluginData, done) {
        const map = new MappedList(symbols, this);
        const replacements = map.groupItemsBySymbolFile((acc, item) => acc[item.resource] = item.useUrl);
        htmlPluginData.html = replaceSpritePlaceholder(htmlPluginData.html, replacements);
        done(null, htmlPluginData);
      });

      // Create sprite chunk
      compilation.plugin('additional-assets', function emitSpriteChunks(done) {
        const map = new MappedList(symbols, this);
        const itemsBySprite = map.groupItemsBySpriteFilename();
        const filenames = Object.keys(itemsBySprite);

        return Promise.map(filenames, (filename) => {
          const spriteSymbols = itemsBySprite[filename].map(item => item.symbol);

          return Sprite.create({ symbols: spriteSymbols })
            .then((sprite) => {
              const content = sprite.render();
              const chunkName = filename.replace(/\.svg$/, '');
              const chunk = new Chunk(chunkName);
              chunk.ids = [];
              chunk.files.push(filename);

              compilation.assets[filename] = {
                source() { return content; },
                size() { return content.length; }
              };

              compilation.chunks.push(chunk);
            });
        }).then(() => {
          done();
          return true;
        }).catch(e => done(e));
      });
    });
  }
}

module.exports = SVGSpritePlugin;
<|endoftext|>


<commit_before>/* eslint-disable import/no-extraneous-dependencies */
const merge = require('deepmerge');
const Promise = require('bluebird');
const Chunk = require('webpack/lib/Chunk');
const SVGCompiler = require('svg-baker');
const spriteFactory = require('svg-baker/lib/sprite-factory');
const Sprite = require('svg-baker/lib/sprite');
const { NAMESPACE } = require('./config');
const {
  MappedList,
  replaceInModuleSource,
  replaceSpritePlaceholder,
  getWebpackVersion
} = require('./utils');

const webpackVersion = parseInt(getWebpackVersion(), 10);

const defaultConfig = {
  plainSprite: false
};

class SVGSpritePlugin {
  constructor(config = {}) {
    this.config = merge.all([defaultConfig, config]);
    const { plainSprite } = this.config;

    const spriteFactoryOptions = {
      styles: plainSprite === false,
      usages: plainSprite === false
    };

    this.factory = ({ symbols }) => {
      const opts = merge.all([spriteFactoryOptions, { symbols }]);
      return spriteFactory(opts);
    };

    this.svgCompiler = new SVGCompiler();
  }

  /**
   * This need to find plugin from loader context
   */
  // eslint-disable-next-line class-methods-use-this
  get NAMESPACE() {
    return NAMESPACE;
  }

  // TODO optimize MappedList instantiation in each hook
  apply(compiler) {
    const plugin = this;
    const { symbols } = this.svgCompiler;

    // Handle only main compilation
    compiler.plugin('this-compilation', (compilation) => {
      // Share svgCompiler with loader
      compilation.plugin('normal-module-loader', (loaderContext) => {
        loaderContext[NAMESPACE] = plugin;
      });

      // Replace placeholders with real URL to symbol (in modules processed by sprite-loader)
      compilation.plugin('after-optimize-chunks', function replacePlaceholdersInModules() {
        const map = new MappedList(symbols, this);
        const replacements = map.groupItemsBySymbolFile((acc, item) => acc[item.resource] = item.useUrl);
        map.items.forEach(item => replaceInModuleSource(item.module, replacements));
      });

      // Hook into extract-text-webpack-plugin event to replace placeholders with real URL to symbol
      compilation.plugin('optimize-extracted-chunks', function replacePlaceholdersInExtractedChunks(chunks) {
        const map = new MappedList(symbols, this);
        const replacements = map.groupItemsBySymbolFile((acc, item) => acc[item.resource] = item.useUrl);

        chunks.forEach((chunk) => {
          const modules = webpackVersion < 3 ? chunk.modules : chunk.mapModules();

          modules
            // dirty hack to identify modules extracted by extract-text-webpack-plugin
            // TODO refactor
            .filter(module => '_originalModule' in module)
            .forEach(module => replaceInModuleSource(module, replacements));
        });
      });

      // Hook into html-webpack-plugin event to replace placeholders with real URL to symbol
      compilation.plugin('html-webpack-plugin-before-html-processing', function htmlPluginHook(htmlPluginData, done) {
        const map = new MappedList(symbols, this);
        const replacements = map.groupItemsBySymbolFile((acc, item) => acc[item.resource] = item.useUrl);
        htmlPluginData.html = replaceSpritePlaceholder(htmlPluginData.html, replacements);
        done(null, htmlPluginData);
      });

      // Create sprite chunk
      compilation.plugin('additional-assets', function emitSpriteChunks(done) {
        const map = new MappedList(symbols, this);
        const itemsBySprite = map.groupItemsBySpriteFilename();
        const filenames = Object.keys(itemsBySprite);

        return Promise.map(filenames, (filename) => {
          const spriteSymbols = itemsBySprite[filename].map(item => item.symbol);

          return Sprite.create({
            symbols: spriteSymbols,
            factory: plugin.factory
          })
            .then((sprite) => {
              const content = sprite.render();
              const chunkName = filename.replace(/\.svg$/, '');
              const chunk = new Chunk(chunkName);
              chunk.ids = [];
              chunk.files.push(filename);

              compilation.assets[filename] = {
                source() { return content; },
                size() { return content.length; }
              };

              compilation.chunks.push(chunk);
            });
        }).then(() => {
          done();
          return true;
        }).catch(e => done(e));
      });
    });
  }
}

module.exports = SVGSpritePlugin;
<commit_msg>refactor(plugin): update options configuring in plain sprite mode<commit_after>/* eslint-disable import/no-extraneous-dependencies */
const merge = require('deepmerge');
const Promise = require('bluebird');
const Chunk = require('webpack/lib/Chunk');
const SVGCompiler = require('svg-baker');
const spriteFactory = require('svg-baker/lib/sprite-factory');
const Sprite = require('svg-baker/lib/sprite');
const { NAMESPACE } = require('./config');
const {
  MappedList,
  replaceInModuleSource,
  replaceSpritePlaceholder,
  getWebpackVersion
} = require('./utils');

const webpackVersion = parseInt(getWebpackVersion(), 10);

const defaultConfig = {
  plainSprite: false
};

class SVGSpritePlugin {
  constructor(config = {}) {
    this.config = merge.all([defaultConfig, config]);
    const { plainSprite } = this.config;

    const spriteFactoryOptions = {};

    if (plainSprite) {
      spriteFactoryOptions.styles = false;
      spriteFactoryOptions.usages = false;
    }

    this.factory = ({ symbols }) => {
      const opts = merge.all([spriteFactoryOptions, { symbols }]);
      return spriteFactory(opts);
    };

    this.svgCompiler = new SVGCompiler();
  }

  /**
   * This need to find plugin from loader context
   */
  // eslint-disable-next-line class-methods-use-this
  get NAMESPACE() {
    return NAMESPACE;
  }

  // TODO optimize MappedList instantiation in each hook
  apply(compiler) {
    const plugin = this;
    const { symbols } = this.svgCompiler;

    // Handle only main compilation
    compiler.plugin('this-compilation', (compilation) => {
      // Share svgCompiler with loader
      compilation.plugin('normal-module-loader', (loaderContext) => {
        loaderContext[NAMESPACE] = plugin;
      });

      // Replace placeholders with real URL to symbol (in modules processed by sprite-loader)
      compilation.plugin('after-optimize-chunks', function replacePlaceholdersInModules() {
        const map = new MappedList(symbols, this);
        const replacements = map.groupItemsBySymbolFile((acc, item) => acc[item.resource] = item.useUrl);
        map.items.forEach(item => replaceInModuleSource(item.module, replacements));
      });

      // Hook into extract-text-webpack-plugin event to replace placeholders with real URL to symbol
      compilation.plugin('optimize-extracted-chunks', function replacePlaceholdersInExtractedChunks(chunks) {
        const map = new MappedList(symbols, this);
        const replacements = map.groupItemsBySymbolFile((acc, item) => acc[item.resource] = item.useUrl);

        chunks.forEach((chunk) => {
          const modules = webpackVersion < 3 ? chunk.modules : chunk.mapModules();

          modules
            // dirty hack to identify modules extracted by extract-text-webpack-plugin
            // TODO refactor
            .filter(module => '_originalModule' in module)
            .forEach(module => replaceInModuleSource(module, replacements));
        });
      });

      // Hook into html-webpack-plugin event to replace placeholders with real URL to symbol
      compilation.plugin('html-webpack-plugin-before-html-processing', function htmlPluginHook(htmlPluginData, done) {
        const map = new MappedList(symbols, this);
        const replacements = map.groupItemsBySymbolFile((acc, item) => acc[item.resource] = item.useUrl);
        htmlPluginData.html = replaceSpritePlaceholder(htmlPluginData.html, replacements);
        done(null, htmlPluginData);
      });

      // Create sprite chunk
      compilation.plugin('additional-assets', function emitSpriteChunks(done) {
        const map = new MappedList(symbols, this);
        const itemsBySprite = map.groupItemsBySpriteFilename();
        const filenames = Object.keys(itemsBySprite);

        return Promise.map(filenames, (filename) => {
          const spriteSymbols = itemsBySprite[filename].map(item => item.symbol);

          return Sprite.create({
            symbols: spriteSymbols,
            factory: plugin.factory
          })
            .then((sprite) => {
              const content = sprite.render();
              const chunkName = filename.replace(/\.svg$/, '');
              const chunk = new Chunk(chunkName);
              chunk.ids = [];
              chunk.files.push(filename);

              compilation.assets[filename] = {
                source() { return content; },
                size() { return content.length; }
              };

              compilation.chunks.push(chunk);
            });
        }).then(() => {
          done();
          return true;
        }).catch(e => done(e));
      });
    });
  }
}

module.exports = SVGSpritePlugin;
<|endoftext|>


<commit_before>var fs = require('fs');
var path = require('path');
var _ = require('lodash');

function ManifestPlugin(opts) {
  this.opts = _.assign({
    basePath: '',
    fileName: 'manifest.json',
    stripSrc: null,
    imageExtensions: /(jpe?g|png|gif|svg)$/i
  }, opts || {});
};

ManifestPlugin.prototype.getFileType = function(str) {
  return str.split('.').pop();
};

ManifestPlugin.prototype.apply = function(compiler) {
  var outputName = this.opts.fileName;
  var outputPath = path.join(compiler.options.output.path, outputName);

  compiler.plugin('done', function(stats){
    var stats = stats.toJson();
    var assetsByChunkName = stats.assetsByChunkName;

    var manifest = {};

    _.merge(manifest, Object.keys(assetsByChunkName).reduce(function(reducedObj, srcName){
      var chunkName = assetsByChunkName[srcName];
      var srcName = srcName.replace(this.opts.stripSrc, '')

      if(Array.isArray(chunkName)) {
        var tmp = chunkName.reduce(function(prev, item){
          prev[srcName + '.' + this.getFileType(item)] = item;
          return prev;
        }.bind(this), {});
        return _.merge(reducedObj, tmp);
      }
      else {
        reducedObj[srcName + '.' + this.getFileType(chunkName)] = chunkName;
        return reducedObj;
      }
    }.bind(this), {}));

    // images don't show up in assetsByChunkName.
    // we're getting them this way;
    _.merge(manifest, stats.assets.reduce(function(prevObj, asset){
      var ext = this.getFileType(asset.name);

      if (this.opts.imageExtensions.test(ext)) {
        var trimmedName = asset.name.split('.').shift();
        prevObj[trimmedName + '.' + ext] = asset.name;
      }

      return prevObj;
    }.bind(this), {}))

    // Append optional basepath onto all references.
    // This allows output path to be reflected in the manifest.
    if (this.opts.basePath) {
      manifest = _.reduce(manifest, function(memo, value, key) {
        memo[this.opts.basePath + key] = this.opts.basePath + value;
        return memo;
      }.bind(this), {});
    }

    fs.writeFileSync(outputPath, JSON.stringify(manifest, null, 2));
  }.bind(this));
};

module.exports = ManifestPlugin;
<commit_msg>Fixing semicolons<commit_after>var fs = require('fs');
var path = require('path');
var _ = require('lodash');

function ManifestPlugin(opts) {
  this.opts = _.assign({
    basePath: '',
    fileName: 'manifest.json',
    stripSrc: null,
    imageExtensions: /(jpe?g|png|gif|svg)$/i
  }, opts || {});
}

ManifestPlugin.prototype.getFileType = function(str) {
  return str.split('.').pop();
};

ManifestPlugin.prototype.apply = function(compiler) {
  var outputName = this.opts.fileName;
  var outputPath = path.join(compiler.options.output.path, outputName);

  compiler.plugin('done', function(stats){
    var stats = stats.toJson();
    var assetsByChunkName = stats.assetsByChunkName;

    var manifest = {};

    _.merge(manifest, Object.keys(assetsByChunkName).reduce(function(reducedObj, srcName){
      var chunkName = assetsByChunkName[srcName];
      var srcName = srcName.replace(this.opts.stripSrc, '');

      if(Array.isArray(chunkName)) {
        var tmp = chunkName.reduce(function(prev, item){
          prev[srcName + '.' + this.getFileType(item)] = item;
          return prev;
        }.bind(this), {});
        return _.merge(reducedObj, tmp);
      }
      else {
        reducedObj[srcName + '.' + this.getFileType(chunkName)] = chunkName;
        return reducedObj;
      }
    }.bind(this), {}));

    // images don't show up in assetsByChunkName.
    // we're getting them this way;
    _.merge(manifest, stats.assets.reduce(function(prevObj, asset){
      var ext = this.getFileType(asset.name);

      if (this.opts.imageExtensions.test(ext)) {
        var trimmedName = asset.name.split('.').shift();
        prevObj[trimmedName + '.' + ext] = asset.name;
      }

      return prevObj;
    }.bind(this), {}));

    // Append optional basepath onto all references.
    // This allows output path to be reflected in the manifest.
    if (this.opts.basePath) {
      manifest = _.reduce(manifest, function(memo, value, key) {
        memo[this.opts.basePath + key] = this.opts.basePath + value;
        return memo;
      }.bind(this), {});
    }

    fs.writeFileSync(outputPath, JSON.stringify(manifest, null, 2));
  }.bind(this));
};

module.exports = ManifestPlugin;
<|endoftext|>