<commit_before>// The Grid component allows an element to be located
//  on a grid of tiles
Crafty.c('Grid', {
  init: function() {
    this.attr({
      w: Game.map_grid.tile.width,
      h: Game.map_grid.tile.height
    })
  },

  // Locate this entity at the given position on the grid
  at: function(x, y) {
    if (x === undefined && y === undefined) {
      return { x: this.x/Game.map_grid.tile.width, y: this.y/Game.map_grid.tile.height }
    } else {
      this.attr({ x: x * Game.map_grid.tile.width, y: y * Game.map_grid.tile.height });
      return this;
    }
  }
});

// An "Actor" is an entity that is drawn in 2D on canvas
//  via our logical coordinate grid
Crafty.c('Actor', {
  init: function() {
    this.requires('2D, Canvas, Grid');
  },
});

// A Tree stops motion and can be cut down by the player
Crafty.c('Wall', {
  init: function() {
    this.requires('Actor, Color, Solid')
      .color('rgb(255, 116, 2)'); //FF6402
  },
});

// A Tree stops motion and can be cut down by the player
Crafty.c('Tree', {
  init: function() {
    this.requires('Actor, platoTree, Solid');
  },
});

// A Monster causes all kinds of trouble
Crafty.c('Monster', {
  init: function() {
    this.requires('Actor, platoMonster, Fourway, Solid, Collision')
      .onHit('Solid', this.stopMovement);
  },

  // Stops the movement
  stopMovement: function() {
    console.log("monster stopMovement");
    this._speed = 0;
    if (this._movement) {
      this.x -= this._movement.x;
      this.y -= this._movement.y;
    }
  },

});

// A Chest has either gold or treasure items inside it
Crafty.c('Chest', {
  init: function() {
    this._gold = (1 + 50 * Math.random()) | 0;
    this.requires('Actor, platoChest');
  },

  collect: function() {
    this.destroy();
    Crafty.trigger('ChestVisited', this);
  }
});

// A castle is a tile on the grid that the PC must visit in order to win the game
Crafty.c('Castle', {
  init: function() {
    this.requires('Actor, platoCastle');
  },
});

// This is the player-controlled character
Crafty.c('PlayerCharacter', {
  init: function() {
    this._hitPoints = 1;
    this._gold = 0;
    this._inUseItems = [];
    this._unusedItems = [];

      // creates a player that moves four ways and stops on collision with solid actors
    this.requires('Actor, Fourway, platoPlayer, Solid, Collision')
      .fourway(4)
      .onHit('Solid', this.stopMovement)
      .onHit('Chest', this.visitChest)
      .bind('Moved', this.attractMonsters);
  },

  attractMonsters: function() {
    var player = this;

    Crafty('Monster').each(function (index) {
      var goNorth = (player.at().y < this.at().y);
      var goSouth = (player.at().y > this.at().y);
      var goEast = (player.at().x > this.at().x);
      var goWest = (player.at().x < this.at().x);

      this._movement = {x: 0, y: 0};

      if (goNorth) { this._movement.y = -4; }
      else if (goSouth) { this._movement.y = 4; }
      else if (goEast) { this._movement.x = 4; }
      else if (goWest) { this._movement.x = -4; }

      this.x += this._movement.x;
      this.y += this._movement.y;
    });
  },

  // Stops the movement
  stopMovement: function() {
    console.log("player stopMovement");
    this._speed = 0;
    if (this._movement) {
      this.x -= this._movement.x;
      this.y -= this._movement.y;
    }
  },

  // Respond to this player visiting a chest
  visitChest: function(data) {
    chest = data[0].obj;
    this._gold = this._gold + chest._gold;
    document.getElementById('gold').innerHTML = this._gold;
    chest.collect();
  }
});

<commit_msg>explore underpinnings of combat<commit_after>// The Grid component allows an element to be located
//  on a grid of tiles
Crafty.c('Grid', {
  init: function() {
    this.attr({
      w: Game.map_grid.tile.width,
      h: Game.map_grid.tile.height
    })
  },

  // Locate this entity at the given position on the grid
  at: function(x, y) {
    if (x === undefined && y === undefined) {
      return { x: this.x/Game.map_grid.tile.width, y: this.y/Game.map_grid.tile.height }
    } else {
      this.attr({ x: x * Game.map_grid.tile.width, y: y * Game.map_grid.tile.height });
      return this;
    }
  }
});

// An "Actor" is an entity that is drawn in 2D on canvas
//  via our logical coordinate grid
Crafty.c('Actor', {
  init: function() {
    this.requires('2D, Canvas, Grid');
  },
});

// A Tree stops motion and can be cut down by the player
Crafty.c('Wall', {
  init: function() {
    this.requires('Actor, Color, Solid')
      .color('rgb(255, 116, 2)'); //FF6402
  },
});

// A Tree stops motion and can be cut down by the player
Crafty.c('Tree', {
  init: function() {
    this.requires('Actor, platoTree, Solid');
  },
});

// A Monster causes all kinds of trouble
Crafty.c('Monster', {
  init: function() {
    // set up hit points for combat
    this._hitPoints = 1 + (5 * Math.random() | 0);

    this.requires('Actor, platoMonster, Fourway, Solid, Collision')
      .onHit('PlayerCharacter', this.attackPlayerCharacter)
      .onHit('Solid', this.stopMovement);
  },

  attackPlayerCharacter: function(data) {
    console.log("monster with " + this._hitPoints + " attack Player with " + data[0].obj._hitPoints + " hitpoints");
  },

  // Stops the movement
  stopMovement: function() {
    console.log("monster stopMovement");
    this._speed = 0;
    if (this._movement) {
      this.x -= this._movement.x;
      this.y -= this._movement.y;
    }
  },

});

// A Chest has either gold or treasure items inside it
Crafty.c('Chest', {
  init: function() {
    this._gold = (1 + 50 * Math.random()) | 0;
    this.requires('Actor, platoChest');
  },

  collect: function() {
    this.destroy();
    Crafty.trigger('ChestVisited', this);
  }
});

// A castle is a tile on the grid that the PC must visit in order to win the game
Crafty.c('Castle', {
  init: function() {
    this.requires('Actor, platoCastle');
  },
});

// This is the player-controlled character
Crafty.c('PlayerCharacter', {
  init: function() {
    // from the turbo pascal: "exp := 24.0; arrows := 20; gold := 10; which_swd := 0;"

    this._hitPoints = 24;
    this._gold = 10;
    this._arrows = 10;
    this._inUseItems = [];
    this._unusedItems = [];

      // creates a player that moves four ways and stops on collision with solid actors
    this.requires('Actor, Fourway, platoPlayer, Solid, Collision')
      .fourway(4)
      .onHit('Solid', this.stopMovement)
      .onHit('Monster', this.attackMonster)
      .onHit('Chest', this.visitChest)
      .bind('Moved', this.attractMonsters);
  },

  attractMonsters: function() {
    var player = this;

    Crafty('Monster').each(function (index) {
      var goNorth = (player.at().y < this.at().y);
      var goSouth = (player.at().y > this.at().y);
      var goEast = (player.at().x > this.at().x);
      var goWest = (player.at().x < this.at().x);

      this._movement = {x: 0, y: 0};

      if (goNorth) { this._movement.y = -4; }
      else if (goSouth) { this._movement.y = 4; }
      else if (goEast) { this._movement.x = 4; }
      else if (goWest) { this._movement.x = -4; }

      this.x += this._movement.x;
      this.y += this._movement.y;
    });
  },

  // Attacks the monster
  attackMonster: function(data) {
    console.log("player with " + this._hitPoints + " attack monster with " + data[0].obj._hitPoints + " hitpoints");
  },

  // Stops the movement
  stopMovement: function() {
    console.log("player stopMovement");
    this._speed = 0;
    if (this._movement) {
      this.x -= this._movement.x;
      this.y -= this._movement.y;
    }
  },

  // Respond to this player visiting a chest
  visitChest: function(data) {
    chest = data[0].obj;
    this._gold = this._gold + chest._gold;
    document.getElementById('gold').innerHTML = this._gold;
    chest.collect();
  }
});

<|endoftext|>


<commit_before>$(document).ready(function () {
  var timeData = [],
    temperatureData = [],
    humidityData = [],
  pm2Data = [],
  pm10Data = [];
 //---------yanji start 1/2------------

  //20170913
  var pm10Data = [],
      pm25Data = [];
  var result;
 // android 20170912 23:29
  var humilength = humidityData.length;
  // 20170913
  var templength = temperatureData.length;
  var pm10length = pm10Data.length;
  var pm25length = pm25Data.length;
  var rpmData = [];
  var rpm2Data = [];

   var aaaa = 50;
  //---------yanji end 1/2------------
  document.getElementById("pm2").innerHTML = "50";
  document.getElementById("pm10").innerHTML = "30";
  document.getElementById("temp").innerHTML = "10";
  document.getElementById("humi").innerHTML = "80";
  document.getElementById("rpm").innerHTML = "1200";
  document.getElementById("rpm2").innerHTML = "1000";
  var data = {
    labels: timeData,
    datasets: [
      {
        fill: false,
        label: 'Temp',
        yAxisID: 'Temperature',
        borderColor: "rgba(255, 204, 0, 1)",
        pointBoarderColor: "rgba(255, 204, 0, 1)",
        backgroundColor: "rgba(255, 204, 0, 0.4)",
        pointHoverBackgroundColor: "rgba(255, 204, 0, 1)",
        pointHoverBorderColor: "rgba(255, 204, 0, 1)",
        data: temperatureData
      },
      {
        fill: false,
        label: 'pm2.5',
        yAxisID: 'pm2.5',
        borderColor: "rgba(255, 131, 131, 1)",
        pointBoarderColor: "rgba(255, 131, 131, 1)",
        backgroundColor: "rgba(255, 131, 131, 0.4)",
        pointHoverBackgroundColor: "rgba(255, 131, 131, 1)",
        pointHoverBorderColor: "rgba(255, 131, 131, 1)",
        data: pm2Data
      }
    ]
  }

  var basicOption = {
    title: {
      display: true,
      text: 'Temperature & pm2.5 Real-time Data',
      fontSize: 15
    },
    scales: {
      yAxes: [{
        id: 'Temperature',
        type: 'linear',
        scaleLabel: {
          labelString: 'Temp(C)',
          display: true
        },
        position: 'left',
      }, {
          id: 'pm2.5',
          type: 'linear',
          scaleLabel: {
            labelString: 'pm2.5(ug/m3)',
            display: true
          },
          position: 'right'
        }]
    }
  }

  ///////////////////////////////////////////////////////////////////////////////////////////////////

  var data2 = {
    labels: timeData,
    datasets: [
      {
        fill: false,
        label: 'Motor1',
        yAxisID: 'Motor1',
        borderColor: "rgba(255, 204, 0, 1)",
        pointBoarderColor: "rgba(255, 204, 0, 1)",
        backgroundColor: "rgba(255, 204, 0, 0.4)",
        pointHoverBackgroundColor: "rgba(255, 204, 0, 1)",
        pointHoverBorderColor: "rgba(255, 204, 0, 1)",
        data: rpmData
      },
      {
        fill: false,
        label: 'Motor2',
        yAxisID: 'Motor2',
        borderColor: "rgba(255, 131, 131, 1)",
        pointBoarderColor: "rgba(255, 131, 131, 1)",
        backgroundColor: "rgba(255, 131, 131, 0.4)",
        pointHoverBackgroundColor: "rgba(255, 131, 131, 1)",
        pointHoverBorderColor: "rgba(255, 131, 131, 1)",
        data: rpm2Data
      }
    ]
  }

  var basicOption2 = {
    title: {
      display: true,
      text: 'Mortor1 & Mortor2 Real-time Data',
      fontSize: 15
    },
    scales: {
      yAxes: [{
        id: 'Motor1',
        type: 'linear',
        scaleLabel: {
          labelString: 'rpm(r/min)',
          display: true
        },
        position: 'left',
      }, {
          id: 'Motor2',
          type: 'linear',
          scaleLabel: {
            labelString: 'rpm(r/min)',
            display: true
          },
          position: 'right'
        }]
    }
  }

  ///////////////////////////////////////////////////////////////////////////////////////////////////

  //Get the context of the canvas element we want to select
  var ctx = document.getElementById("myChart").getContext("2d");
  var optionsNoAnimation = { animation: false }
  var myLineChart = new Chart(ctx, {
    type: 'line',
    data: data,
    options: basicOption
  });
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  var ctx2 = document.getElementById("myChart2").getContext("2d");
  var optionsNoAnimation2 = { animation: false }
  var myLineChart2 = new Chart(ctx2, {
    type: 'line',
    data: data2,
    options: basicOption2
  });
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  var ws = new WebSocket('wss://' + location.host);
  ws.onopen = function () {
    console.log('Successfully connect WebSocket');
  }
  ws.onmessage = function (message) {
    console.log('receive message' + message.data);
    try {
      var obj = JSON.parse(message.data);
      if(!obj.time || !obj.params.Temperature) {
        return;
      }
      document.getElementById("pm2").innerHTML = obj.params.pm2;
  	  document.getElementById("pm10").innerHTML = obj.params.pm10;
	  document.getElementById("temp").innerHTML = obj.params.Temperature;
	  document.getElementById("humi").innerHTML = obj.params.Humidity;
	  document.getElementById("rpm").innerHTML = obj.params.rpm;
	  document.getElementById("rpm2").innerHTML = obj.params.rpm2;
	  var time = obj.time;
	  var subS = time.substring(11,19);
      timeData.push(subS);
      temperatureData.push(obj.params.Temperature);
      // only keep no more than 50 points in the line chart
      const maxLen = 10;
      var len = timeData.length;
      if (len > maxLen) {
        timeData.shift();
        temperatureData.shift();
      }

      if (obj.params.Humidity) {
        humidityData.push(obj.params.Humidity);
      }
      if (humidityData.length > maxLen) {
        humidityData.shift();
      }
	  if (obj.params.pm2) {
		  pm2Data.push(obj.params.pm2);
		  rpmData.push(obj.params.rpm);
		  rpm2Data.push(obj.params.rpm2);
	  }
	  if (pm2Data.length > maxLen)
	  {
		  pm2Data.shift();
	  }

	  if (rpmData.length > maxLen)
	  {
		  rpmData.shift();
	  }
	  if (rpm2Data.length > maxLen)
	  {
		  rpm2Data.shift();
	  }

 myLineChart2.update();

      myLineChart.update();

 //---------yanji start 2/2------------
  //20170913 pm Data push
      pm10Data.push(obj.params.pm10);
      pm25Data.push(obj.params.pm2);

      //android 20170912 23:29
//       if(humilength==0 || templength == 0 || pm10length ==0 || pm25length ==0){

//            }
//           }else{
//             humilength = humidityData.length;
//             templength = temperatureData.length;
//             pm10length = pm10Data.length;
//             pm25length = pm25Data.length;
//            insertDatas(pm25Data[pm25length],humidityData[humilength],temperatureData[templength],humidityData[humilength]);
//         }
      //20170913

     if((pm25length<pm25Data.length || pm25length == pm25Data.length)&&(pm10length<pm10Data.length || pm10length == pm10Data.length)&&(templength<temperatureData.length || templength == temperatureData.length)&&(humilength<humidityData.length || humilength == humidityData.length)){
          pm25length = pm25Data.length;
          pm10length = pm10Data.length;
          humilength = humidityData.length;
          templength = temperatureData.length;

          insertDatas(pm25Data[pm25length-1],pm10Data[pm10length-1],temperatureData[templength-1],humidityData[humilength-1]);

          }

      //android 20170912 23:29
      function insertDatas(p2,p1,t,h){
         var p2State;
         if(p2<31){
           p2State = "좋음";
         }else if(p2<81){
           p2State = "보통";
         }else if(p2<151){
           p2State = "나쁨";
         }else{
           p2State = "매우나쁨";
         }
         Ao.showResult(p2,p1,t,h,p2State);
      }

//---------yanji end 2/2------------

    } catch (err) {
      console.error(err);
    }
  }
});
<commit_msg>Update index.js<commit_after>$(document).ready(function () {
  var timeData = [],
    temperatureData = [],
    humidityData = [],
  pm2Data = [],
  pm10Data = [];
 //---------yanji start 1/2------------

  //20170913
  var pm10Data = [],
      pm25Data = [];
  var result;
 // android 20170912 23:29
  var humilength = humidityData.length;
  // 20170913
  var templength = temperatureData.length;
  var pm10length = pm10Data.length;
  var pm25length = pm25Data.length;
  var rpmData = [];
  var rpm2Data = [];

   var aaaa = 50;
  //---------yanji end 1/2------------
  document.getElementById("pm2").innerHTML = "50";
  document.getElementById("pm10").innerHTML = "30";
  document.getElementById("temp").innerHTML = "10";
  document.getElementById("humi").innerHTML = "80";
  document.getElementById("rpm").innerHTML = "1200";
  document.getElementById("rpm2").innerHTML = "1000";
  var data = {
    labels: timeData,
    datasets: [
      {
        fill: false,
        label: 'Temp',
        yAxisID: 'Temperature',
        borderColor: "rgba(255, 204, 0, 1)",
        pointBoarderColor: "rgba(255, 204, 0, 1)",
        backgroundColor: "rgba(255, 204, 0, 0.4)",
        pointHoverBackgroundColor: "rgba(255, 204, 0, 1)",
        pointHoverBorderColor: "rgba(255, 204, 0, 1)",
        data: temperatureData
      },
      {
        fill: false,
        label: 'pm2.5',
        yAxisID: 'pm2.5',
        borderColor: "rgba(255, 131, 131, 1)",
        pointBoarderColor: "rgba(255, 131, 131, 1)",
        backgroundColor: "rgba(255, 131, 131, 0.4)",
        pointHoverBackgroundColor: "rgba(255, 131, 131, 1)",
        pointHoverBorderColor: "rgba(255, 131, 131, 1)",
        data: pm2Data
      }
    ]
  }

  var basicOption = {
    title: {
      display: true,
      text: 'Temperature & pm2.5 Real-time Data',
      fontSize: 15
    },
    scales: {
      yAxes: [{
        id: 'Temperature',
        type: 'linear',
        scaleLabel: {
          labelString: 'Temp(C)',
          display: true
        },
        position: 'left',
      }, {
          id: 'pm2.5',
          type: 'linear',
          scaleLabel: {
            labelString: 'pm2.5(ug/m3)',
            display: true
          },
          position: 'right'
        }]
    }
  }

  ///////////////////////////////////////////////////////////////////////////////////////////////////

  var data2 = {
    labels: timeData,
    datasets: [
      {
        fill: false,
        label: 'Motor1',
        yAxisID: 'Motor1',
        borderColor: "rgba(0, 84, 255, 1)",
        pointBoarderColor: "rgba(0, 84, 255, 1)",
        backgroundColor: "rgba(0, 84, 255, 0.4)",
        pointHoverBackgroundColor: "rgba(0, 84, 255, 1)",
        pointHoverBorderColor: "rgba(0, 84, 255, 1)",
        data: rpmData
      },
      {
        fill: false,
        label: 'Motor2',
        yAxisID: 'Motor2',
        borderColor: "rgba(29, 219, 22, 1)",
        pointBoarderColor: "rgba(29, 219, 22, 1)",
        backgroundColor: "rgba(29, 219, 22, 0.4)",
        pointHoverBackgroundColor: "rgba(29, 219, 22, 1)",
        pointHoverBorderColor: "rgba(29, 219, 22, 1)",
        data: rpm2Data
      }
    ]
  }

  var basicOption2 = {
    title: {
      display: true,
      text: 'Mortor1 & Mortor2 Real-time Data',
      fontSize: 15
    },
    scales: {
      yAxes: [{
        id: 'Motor1',
        type: 'linear',
        scaleLabel: {
          labelString: 'rpm(r/min)',
          display: true
        },
        position: 'left',
      }, {
          id: 'Motor2',
          type: 'linear',
          scaleLabel: {
            labelString: 'rpm(r/min)',
            display: true
          },
          position: 'right'
        }]
    }
  }

  ///////////////////////////////////////////////////////////////////////////////////////////////////

  //Get the context of the canvas element we want to select
  var ctx = document.getElementById("myChart").getContext("2d");
  var optionsNoAnimation = { animation: false }
  var myLineChart = new Chart(ctx, {
    type: 'line',
    data: data,
    options: basicOption
  });
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  var ctx2 = document.getElementById("myChart2").getContext("2d");
  var optionsNoAnimation2 = { animation: false }
  var myLineChart2 = new Chart(ctx2, {
    type: 'line',
    data: data2,
    options: basicOption2
  });
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  var ws = new WebSocket('wss://' + location.host);
  ws.onopen = function () {
    console.log('Successfully connect WebSocket');
  }
  ws.onmessage = function (message) {
    console.log('receive message' + message.data);
    try {
      var obj = JSON.parse(message.data);
      if(!obj.time || !obj.params.Temperature) {
        return;
      }
      document.getElementById("pm2").innerHTML = obj.params.pm2;
  	  document.getElementById("pm10").innerHTML = obj.params.pm10;
	  document.getElementById("temp").innerHTML = obj.params.Temperature;
	  document.getElementById("humi").innerHTML = obj.params.Humidity;
	  document.getElementById("rpm").innerHTML = obj.params.rpm;
	  document.getElementById("rpm2").innerHTML = obj.params.rpm2;
	  var time = obj.time;
	  var subS = time.substring(11,19);
      timeData.push(subS);
      temperatureData.push(obj.params.Temperature);
      // only keep no more than 50 points in the line chart
      const maxLen = 10;
      var len = timeData.length;
      if (len > maxLen) {
        timeData.shift();
        temperatureData.shift();
      }

      if (obj.params.Humidity) {
        humidityData.push(obj.params.Humidity);
      }
      if (humidityData.length > maxLen) {
        humidityData.shift();
      }
	  if (obj.params.pm2) {
		  pm2Data.push(obj.params.pm2);
		  rpmData.push(obj.params.rpm);
		  rpm2Data.push(obj.params.rpm2);
	  }
	  if (pm2Data.length > maxLen)
	  {
		  pm2Data.shift();
	  }

	  if (rpmData.length > maxLen)
	  {
		  rpmData.shift();
	  }
	  if (rpm2Data.length > maxLen)
	  {
		  rpm2Data.shift();
	  }

 myLineChart2.update();

      myLineChart.update();

 //---------yanji start 2/2------------
  //20170913 pm Data push
      pm10Data.push(obj.params.pm10);
      pm25Data.push(obj.params.pm2);

      //android 20170912 23:29
//       if(humilength==0 || templength == 0 || pm10length ==0 || pm25length ==0){

//            }
//           }else{
//             humilength = humidityData.length;
//             templength = temperatureData.length;
//             pm10length = pm10Data.length;
//             pm25length = pm25Data.length;
//            insertDatas(pm25Data[pm25length],humidityData[humilength],temperatureData[templength],humidityData[humilength]);
//         }
      //20170913

     if((pm25length<pm25Data.length || pm25length == pm25Data.length)&&(pm10length<pm10Data.length || pm10length == pm10Data.length)&&(templength<temperatureData.length || templength == temperatureData.length)&&(humilength<humidityData.length || humilength == humidityData.length)){
          pm25length = pm25Data.length;
          pm10length = pm10Data.length;
          humilength = humidityData.length;
          templength = temperatureData.length;

          insertDatas(pm25Data[pm25length-1],pm10Data[pm10length-1],temperatureData[templength-1],humidityData[humilength-1]);

          }

      //android 20170912 23:29
      function insertDatas(p2,p1,t,h){
         var p2State;
         if(p2<31){
           p2State = "좋음";
         }else if(p2<81){
           p2State = "보통";
         }else if(p2<151){
           p2State = "나쁨";
         }else{
           p2State = "매우나쁨";
         }
         Ao.showResult(p2,p1,t,h,p2State);
      }

//---------yanji end 2/2------------

    } catch (err) {
      console.error(err);
    }
  }
});
<|endoftext|>


<commit_before>/*
 *
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 *
*/

    // Possible field names for various platforms.
    // Some field names are platform specific

    var fieldType = {
        addresses:      "addresses",
        birthday:       "birthday",
        categories:     "categories",
        country:        "country",
        department:     "department",
        displayName:    "displayName",
        emails:         "emails",
        familyName:     "familyName",
        formatted:      "formatted",
        givenName:      "givenName",
        honorificPrefix: "honorificPrefix",
        honorificSuffix: "honorificSuffix",
        id:             "id",
        ims:            "ims",
        locality:       "locality",
        middleName:     "middleName",
        name:           "name",
        nickname:       "nickname",
        note:           "note",
        organizations:  "organizations",
        phoneNumbers:   "phoneNumbers",
        photos:         "photos",
        postalCode:     "postalCode",
        region:         "region",
        streetAddress:  "streetAddress",
        title:          "title",
        urls:           "urls"
    };

    module.exports = fieldType;
<commit_msg>Removing a stray unicode character<commit_after>/*
 *
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 *
*/

    // Possible field names for various platforms.
    // Some field names are platform specific

    var fieldType = {
        addresses:      "addresses",
        birthday:       "birthday",
        categories:     "categories",
        country:        "country",
        department:     "department",
        displayName:    "displayName",
        emails:         "emails",
        familyName:     "familyName",
        formatted:      "formatted",
        givenName:      "givenName",
        honorificPrefix: "honorificPrefix",
        honorificSuffix: "honorificSuffix",
        id:             "id",
        ims:            "ims",
        locality:       "locality",
        middleName:     "middleName",
        name:           "name",
        nickname:       "nickname",
        note:           "note",
        organizations:  "organizations",
        phoneNumbers:   "phoneNumbers",
        photos:         "photos",
        postalCode:     "postalCode",
        region:         "region",
        streetAddress:  "streetAddress",
        title:          "title",
        urls:           "urls"
    };

    module.exports = fieldType;
<|endoftext|>


<commit_before>'use strict';

// --- Dependencies ---

var Stream = require('stream');
var assert = require('assert');

var https  = require('https');
var http   = require('http');

var inherits = require('util').inherits;
var qs = require('querystring');
var url = require('url');

var debug = require('debug')('zotero:message');
var mime = require('mime-types');

var utils = require('./utils');
var downcase = utils.downcase;
var extend = utils.extend;
var noop = utils.noop;

/** @module zotero */

/**
 * A Zotero API message contains an API response and
 * the corresponding request object.
 *
 * @class Message
 * @constructor
 * @extends Stream
 *
 * @params {Object} [options] For `https.request`.
 * @params {Object} [body] Optional request body.
 */
function Message(options, body) {
  Stream.call(this);

  if (options || body)
    this.bind(options, body);

}

inherits(Message, Stream);

// Limit the number of redirects to follow to avoid loops!
Message.MAX_REDIRECTS = 5;

Object.defineProperties(Message.prototype, {

  /**
   * The full API path called by the message.
   *
   * @property path
   * @type String
   */
  path: {
    get: function () {
      return this.req && this.req.path;
    }
  },

  /**
   * The encoding of the response body, inferred from
   * the response's `Content-Type` header.
   *
   * @property encoding
   * @type String
   */
  encoding: {
    get: function () {
      if (!this.headers) return undefined;
      return mime.charset(this.headers['content-type']);
    }
  },

  /**
   * The file type of the response body's content, inferred
   * from the response's `Content-Type` header.
   *
   * This value is used to select a suitable message parser.
   *
   * @property type
   * @type String
   */
  type: {
    get: function () {
      if (!this.headers) return undefined;
      return mime.extension(this.headers['content-type']) || undefined;
    }
  },

  /**
   * The total number of results for this request for
   * multi-object requests; undefined for other requests.
   *
   * @property total
   * @type Number
   */
  total: {
    get: function () {
      if (!this.headers) return undefined;
      return parseInt(this.headers['total-results'], 10);
    }
  },

  /**
   * Whether or not this is a multi-object response.
   * This is the case if the `Total-Results` header
   * is present.
   *
   * @property multi
   * @type Boolean
   */
  multi: {
    get: function () {
      return this.total !== undefined;
    }
  },

  /**
   * Whether or not there are more objects available
   * for the Message's path. This is the case if a
   * `next` URL is present in the response's `Link`
   * header.
   *
   * @property done
   * @type Boolean
   */
  done: {
    get: function () {
      return !(this.links && this.links.next);
    }
  },

  /**
   * The `Last-Modified-Version` header as an integer.
   *
   * @property version
   * @type Number
   */
  version: {
    get: function () {
      if (!this.headers) return undefined;
      return parseInt(this.headers['last-modified-version'], 10);
    }
  },

  /**
   * The response's HTTP status code.
   *
   * @property code
   * @type Number
   */
  code: {
    get: function () {
      return this.res && this.res.statusCode;
    }
  },

  /**
   * Whether or not the response message has an OK status.
   *
   * @property ok
   * @type Boolean
   */
  ok: {
    get: function () { return this.code >= 200 && this.code < 300; }
  },

  /**
   * Whether or not the response message was not modified.
   *
   * @property unmodified
   * @type Boolean
   */
  unmodified: {
    get: function () { return this.code === 304; }
  },

  /**
   * If the HTTP response was not OK, returns an associated
   * error object; undefined otherwise.
   *
   * @property error
   * @type Error
   */
  error: {
    get: function () {
      if (!this.received || this.ok || this.unmodified)
        return undefined;

      var error = new Error(this.text || 'unknown');
      error.code = this.code;

      return error;
    }
  },

  text: {
    get: function () {
      switch (this.type) {
      case 'txt':
      case 'html':
        return this.data;
      default:
        return undefined;
      }
    }
  }
});

// --- Message Prototype Methods ---

/**
 * Resets the message state; use if you need to reuse
 * a message object.
 *
 * @method reset
 * @chainable
 * @private
 */
Message.prototype.reset = function () {
  if (this.req) {
    this.req.removeAllListeners();
    delete this.req;
  }

  if (this.res) {
    this.res.removeAllListeners();
    delete this.res;
  }

  this.redirects = [];

  delete this.client;
  delete this.data;
  delete this.headers;
  delete this.links;
  delete this.options;
  delete this.received;
  delete this.sent;

  return this;
};

/**
 * Creates a new HTTP request and binds it to this
 * message; this will automatically set up the
 * message to receive and bind the corresponding
 * HTTP response as well.
 *
 * If given, the body will be included
 * as a JSON string.
 *
 * @method bind
 * @chainable
 *
 * @params {Object} options For `https.request`.
 * @params {Object|String} [body] Optional request body.
 */
Message.prototype.bind = function (options, body) {
  debug('binding new http request...');

  this.reset();
  var req = h(options).request(options);

  /**
   * The HTTP request options.
   *
   * @property options
   * @type Object
   */
  reader(this, 'options', options);

  /**
   * The corresponding HTTP request.
   *
   * @property req
   * @type http.ClientRequest
   */
  reader(this, 'req', req);

  req.on('response', this.resolve.bind(this));
  req.on('error', this.emit.bind(this, 'error'));

  if (body) {
    if (typeof body === 'string') {
      req.write(body);

    } else {
      req.write(JSON.stringify(body));
      req.setHeader('Content-Type', 'application/json');
    }
  }

  this.emit('request', req);

  return this;
};

/**
 * Resolves HTTP redirects (if any) and before
 * receiving a response.
 *
 * @method resolve
 * @chainable
 * @private
 *
 * @param {http.IncomingMessage} res
 */
Message.prototype.resolve = function (res) {
  if (res.statusCode === 302 || res.statusCode === 301) {
    var target = downcase(res.headers).location;

    if (target && this.redirects.length < Message.MAX_REDIRECTS) {
      debug('following redirect to "%s"...', target);

      var options = url.parse(target);
      var req = h(options).request(options);

      req.on('response', this.resolve.bind(this));
      req.on('error', this.emit.bind(this, 'error'));

      req.end();

      this.redirects.push(req);

      return this;
    }
  }

  return this.receive(res);
};

/**
 * Receives an HTTP response and binds it to
 * this message.
 *
 * @method receive
 * @chainable
 * @private
 *
 * @param {http.IncomingMessage} res
 */
Message.prototype.receive = function (res) {
  debug('http response received...');

  var self = this;
  var data = [];
  var size = 0;

  /**
   * The corresonding HTTP response.
   *
   * @property res
   * @type http.IncomingMessage
   */
  reader(this, 'res', res);

  /**
   * The HTTP response headers.
   *
   * Note: This is a copy of `res.headers`, but all header
   * names are normalized to lower case.
   *
   * @property headers
   * @type Object
   */
  reader(this, 'headers', downcase(res.headers));

  /**
   * The links given in the Message's HTTP `Link` header.
   * Typically, multi-object responses will contain a
   * `next` and `last` link.
   *
   * Each link is an object with a `path` and `options`
   * value that can be passed to `Client#get`.
   *
   * @property links
   * @type Object
   */
  reader(this, 'links', Link.parse(this.headers.link));

  // Receive Data

  function receiving(chunk) {
    size += chunk.length;
    data.push(chunk);
    self.emit('data', chunk);
  }

  res.on('data', receiving);

  res.once('end', function received() {
    debug('response received with status %d: %d bytes read',
      self.code, size);

    res.removeListener('data', receiving);

    data = Buffer.concat(data, size);

    self.parse(data, self.type, function (error, result) {
      if (error) {
        debug('failed to parse data: %s', error.message);
        self.data = data;

      } else {
        self.data = result;
      }

      /**
       * Whether or not the Message has been fully
       * received and parsed.
       *
       * @property received
       * @type Boolean
       */
      reader(self, 'received', true);

      self.emit('received');
    });
  });

  res.on('readable', this.emit.bind(this, 'readable'));
  res.on('close', this.emit.bind(this, 'close'));
  res.on('error', this.emit.bind(this, 'error'));

  this.emit('response', res);

  return this;
};

/**
 * Parses a Zotero API response body if a parser for
 * the content type is available.
 *
 * @method parse
 * @chainable
 *
 * @param {String|Buffer} data The content as a string.
 * @param {String} [type = this.type] The data's content type.
 * @param {Function} callback Will be called with the parse results.
 */
Message.prototype.parse = function (data, type, callback) {
  type = type || this.type;
  callback = callback || noop;

  var p = type && Message.parsers[type];

  if (p) {
    debug('attempting to parse "%s" data', type);
    process.nextTick(p.bind(this, data, callback));

  } else {
    if (type)
      debug('no parser available for "%s"', type);

    process.nextTick(callback.bind(this, false, data));
  }

  return this;
};

/**
 * Sends the Message's request to the Zotero server.
 *
 * @method send
 * @chainable
 *
 * @private
 *
 * @param {Client} [client]
 */
Message.prototype.send = function (client) {
  if (this.sent) return this;

  assert(this.req, 'message not bound to a request yet');

  reader(this, 'client', client);
  this.req.end();

  /**
   * Whether or not the Message has been sent.
   *
   * @property sent
   * @type Boolean
   */
  reader(this, 'sent', true);
  this.emit('sent', this);

  return this;
};

/**
 * Sends a duplicate request to the API server/
 *
 * @param {Function} [callback]
 * @returns {Message} The duplicate message.
 */
Message.prototype.retry = function (callback) {
  assert(this.client, 'cannot retry sending message: no associated client');

  return this.client
    .request(extend({}, this.options), callback);
};

Message.prototype.next = function (callback) {
  if (this.done) return null;

  assert(this.client, 'cannot send next message: no associated client');

  var link = this.links.next;
  var headers = extend({}, this.options.headers);

  return this.client
    .get(link.path, link.options, headers, callback);
};

// --- Stream Implementation ---

Message.prototype.destroy = function () {
  return this.res.destroy.apply(this.res, arguments);
};

Message.prototype.pause = function () {
  return this.res.pause.apply(this.res, arguments);
};

Message.prototype.resume = function () {
  return this.res.resume.apply(this.res, arguments);
};

// --- Message Parsers ---

mime.define({
  'application/vnd.citationstyles.csl+json': ['json']
});

/**
 * The available Zotero API response body parsers.
 *
 * @property parsers
 * @type Object
 * @static
 */
Message.parsers = {
  json: function (data, callback) {
    try {
      var parsed = JSON.parse(data.toString(this.encoding));

    } catch (error) {
      return callback(error, data);
    }

    return callback(null, parsed);

  },

  txt: function (data, callback) {
    try {
      var parsed = data.toString(this.encoding);

    } catch (error) {
      return callback(error, data);
    }

    return callback(null, parsed);
  }
};

Message.parsers.html = Message.parsers.txt;

// --- Helpers ---

function h(options) {
  if ((/^https/i).test(options.protocol)) return https;
  if (options.port === 443) return https;

  return http;
}

function property(obj, name, descriptor) {
  Object.defineProperty(obj, name, descriptor);
}

function reader(obj, name, value) {
  var descriptor = { configurable: true, enumerable: false };

  if (typeof value === 'function') {
    descriptor.get = value;
  } else {
    descriptor.value = value;
  }

  property(obj, name, descriptor);
}

function Link(string) {
  var p = url.parse(string);

  this.path = p.pathname;

  if (p.query)
    this.options = qs.parse(p.query);
}

Link.parse = function (text) {
  var links = {};

  if (text) {
    debug('parsing link header...');

    // Link headers look like this (comma separated):
    // <https://api.zotero.org/users/12345/items?limit=30&start=30>; rel="next"
    var pattern = /<([^>]+)>;\s*rel="([^"]+)"/g;
    var m;

    while ((m = pattern.exec(text)) !== null)
      links[m[2]] = new Link(m[1]);

  }

  return links;
};

reader(Link.prototype, 'url', function () {
  var query = qs.stringify(this.options);
  if (query.length) query = '?' + query;

  return this.path + query;
});

// --- Exports ---
exports = module.exports = Message;
exports.Link = Link;
<commit_msg>call req.setHeaders() before calling req.write(), prevents error "can't set headers after they are sent"<commit_after>'use strict';

// --- Dependencies ---

var Stream = require('stream');
var assert = require('assert');

var https  = require('https');
var http   = require('http');

var inherits = require('util').inherits;
var qs = require('querystring');
var url = require('url');

var debug = require('debug')('zotero:message');
var mime = require('mime-types');

var utils = require('./utils');
var downcase = utils.downcase;
var extend = utils.extend;
var noop = utils.noop;

/** @module zotero */

/**
 * A Zotero API message contains an API response and
 * the corresponding request object.
 *
 * @class Message
 * @constructor
 * @extends Stream
 *
 * @params {Object} [options] For `https.request`.
 * @params {Object} [body] Optional request body.
 */
function Message(options, body) {
  Stream.call(this);

  if (options || body)
    this.bind(options, body);

}

inherits(Message, Stream);

// Limit the number of redirects to follow to avoid loops!
Message.MAX_REDIRECTS = 5;

Object.defineProperties(Message.prototype, {

  /**
   * The full API path called by the message.
   *
   * @property path
   * @type String
   */
  path: {
    get: function () {
      return this.req && this.req.path;
    }
  },

  /**
   * The encoding of the response body, inferred from
   * the response's `Content-Type` header.
   *
   * @property encoding
   * @type String
   */
  encoding: {
    get: function () {
      if (!this.headers) return undefined;
      return mime.charset(this.headers['content-type']);
    }
  },

  /**
   * The file type of the response body's content, inferred
   * from the response's `Content-Type` header.
   *
   * This value is used to select a suitable message parser.
   *
   * @property type
   * @type String
   */
  type: {
    get: function () {
      if (!this.headers) return undefined;
      return mime.extension(this.headers['content-type']) || undefined;
    }
  },

  /**
   * The total number of results for this request for
   * multi-object requests; undefined for other requests.
   *
   * @property total
   * @type Number
   */
  total: {
    get: function () {
      if (!this.headers) return undefined;
      return parseInt(this.headers['total-results'], 10);
    }
  },

  /**
   * Whether or not this is a multi-object response.
   * This is the case if the `Total-Results` header
   * is present.
   *
   * @property multi
   * @type Boolean
   */
  multi: {
    get: function () {
      return this.total !== undefined;
    }
  },

  /**
   * Whether or not there are more objects available
   * for the Message's path. This is the case if a
   * `next` URL is present in the response's `Link`
   * header.
   *
   * @property done
   * @type Boolean
   */
  done: {
    get: function () {
      return !(this.links && this.links.next);
    }
  },

  /**
   * The `Last-Modified-Version` header as an integer.
   *
   * @property version
   * @type Number
   */
  version: {
    get: function () {
      if (!this.headers) return undefined;
      return parseInt(this.headers['last-modified-version'], 10);
    }
  },

  /**
   * The response's HTTP status code.
   *
   * @property code
   * @type Number
   */
  code: {
    get: function () {
      return this.res && this.res.statusCode;
    }
  },

  /**
   * Whether or not the response message has an OK status.
   *
   * @property ok
   * @type Boolean
   */
  ok: {
    get: function () { return this.code >= 200 && this.code < 300; }
  },

  /**
   * Whether or not the response message was not modified.
   *
   * @property unmodified
   * @type Boolean
   */
  unmodified: {
    get: function () { return this.code === 304; }
  },

  /**
   * If the HTTP response was not OK, returns an associated
   * error object; undefined otherwise.
   *
   * @property error
   * @type Error
   */
  error: {
    get: function () {
      if (!this.received || this.ok || this.unmodified)
        return undefined;

      var error = new Error(this.text || 'unknown');
      error.code = this.code;

      return error;
    }
  },

  text: {
    get: function () {
      switch (this.type) {
      case 'txt':
      case 'html':
        return this.data;
      default:
        return undefined;
      }
    }
  }
});

// --- Message Prototype Methods ---

/**
 * Resets the message state; use if you need to reuse
 * a message object.
 *
 * @method reset
 * @chainable
 * @private
 */
Message.prototype.reset = function () {
  if (this.req) {
    this.req.removeAllListeners();
    delete this.req;
  }

  if (this.res) {
    this.res.removeAllListeners();
    delete this.res;
  }

  this.redirects = [];

  delete this.client;
  delete this.data;
  delete this.headers;
  delete this.links;
  delete this.options;
  delete this.received;
  delete this.sent;

  return this;
};

/**
 * Creates a new HTTP request and binds it to this
 * message; this will automatically set up the
 * message to receive and bind the corresponding
 * HTTP response as well.
 *
 * If given, the body will be included
 * as a JSON string.
 *
 * @method bind
 * @chainable
 *
 * @params {Object} options For `https.request`.
 * @params {Object|String} [body] Optional request body.
 */
Message.prototype.bind = function (options, body) {
  debug('binding new http request...');

  this.reset();
  var req = h(options).request(options);

  /**
   * The HTTP request options.
   *
   * @property options
   * @type Object
   */
  reader(this, 'options', options);

  /**
   * The corresponding HTTP request.
   *
   * @property req
   * @type http.ClientRequest
   */
  reader(this, 'req', req);

  req.on('response', this.resolve.bind(this));
  req.on('error', this.emit.bind(this, 'error'));

  if (body) {
    if (typeof body === 'string') {
      req.write(body);

    } else {
      req.setHeader('Content-Type', 'application/json');
      req.write(JSON.stringify(body));
    }
  }

  this.emit('request', req);

  return this;
};

/**
 * Resolves HTTP redirects (if any) and before
 * receiving a response.
 *
 * @method resolve
 * @chainable
 * @private
 *
 * @param {http.IncomingMessage} res
 */
Message.prototype.resolve = function (res) {
  if (res.statusCode === 302 || res.statusCode === 301) {
    var target = downcase(res.headers).location;

    if (target && this.redirects.length < Message.MAX_REDIRECTS) {
      debug('following redirect to "%s"...', target);

      var options = url.parse(target);
      var req = h(options).request(options);

      req.on('response', this.resolve.bind(this));
      req.on('error', this.emit.bind(this, 'error'));

      req.end();

      this.redirects.push(req);

      return this;
    }
  }

  return this.receive(res);
};

/**
 * Receives an HTTP response and binds it to
 * this message.
 *
 * @method receive
 * @chainable
 * @private
 *
 * @param {http.IncomingMessage} res
 */
Message.prototype.receive = function (res) {
  debug('http response received...');

  var self = this;
  var data = [];
  var size = 0;

  /**
   * The corresonding HTTP response.
   *
   * @property res
   * @type http.IncomingMessage
   */
  reader(this, 'res', res);

  /**
   * The HTTP response headers.
   *
   * Note: This is a copy of `res.headers`, but all header
   * names are normalized to lower case.
   *
   * @property headers
   * @type Object
   */
  reader(this, 'headers', downcase(res.headers));

  /**
   * The links given in the Message's HTTP `Link` header.
   * Typically, multi-object responses will contain a
   * `next` and `last` link.
   *
   * Each link is an object with a `path` and `options`
   * value that can be passed to `Client#get`.
   *
   * @property links
   * @type Object
   */
  reader(this, 'links', Link.parse(this.headers.link));

  // Receive Data

  function receiving(chunk) {
    size += chunk.length;
    data.push(chunk);
    self.emit('data', chunk);
  }

  res.on('data', receiving);

  res.once('end', function received() {
    debug('response received with status %d: %d bytes read',
      self.code, size);

    res.removeListener('data', receiving);

    data = Buffer.concat(data, size);

    self.parse(data, self.type, function (error, result) {
      if (error) {
        debug('failed to parse data: %s', error.message);
        self.data = data;

      } else {
        self.data = result;
      }

      /**
       * Whether or not the Message has been fully
       * received and parsed.
       *
       * @property received
       * @type Boolean
       */
      reader(self, 'received', true);

      self.emit('received');
    });
  });

  res.on('readable', this.emit.bind(this, 'readable'));
  res.on('close', this.emit.bind(this, 'close'));
  res.on('error', this.emit.bind(this, 'error'));

  this.emit('response', res);

  return this;
};

/**
 * Parses a Zotero API response body if a parser for
 * the content type is available.
 *
 * @method parse
 * @chainable
 *
 * @param {String|Buffer} data The content as a string.
 * @param {String} [type = this.type] The data's content type.
 * @param {Function} callback Will be called with the parse results.
 */
Message.prototype.parse = function (data, type, callback) {
  type = type || this.type;
  callback = callback || noop;

  var p = type && Message.parsers[type];

  if (p) {
    debug('attempting to parse "%s" data', type);
    process.nextTick(p.bind(this, data, callback));

  } else {
    if (type)
      debug('no parser available for "%s"', type);

    process.nextTick(callback.bind(this, false, data));
  }

  return this;
};

/**
 * Sends the Message's request to the Zotero server.
 *
 * @method send
 * @chainable
 *
 * @private
 *
 * @param {Client} [client]
 */
Message.prototype.send = function (client) {
  if (this.sent) return this;

  assert(this.req, 'message not bound to a request yet');

  reader(this, 'client', client);
  this.req.end();

  /**
   * Whether or not the Message has been sent.
   *
   * @property sent
   * @type Boolean
   */
  reader(this, 'sent', true);
  this.emit('sent', this);

  return this;
};

/**
 * Sends a duplicate request to the API server/
 *
 * @param {Function} [callback]
 * @returns {Message} The duplicate message.
 */
Message.prototype.retry = function (callback) {
  assert(this.client, 'cannot retry sending message: no associated client');

  return this.client
    .request(extend({}, this.options), callback);
};

Message.prototype.next = function (callback) {
  if (this.done) return null;

  assert(this.client, 'cannot send next message: no associated client');

  var link = this.links.next;
  var headers = extend({}, this.options.headers);

  return this.client
    .get(link.path, link.options, headers, callback);
};

// --- Stream Implementation ---

Message.prototype.destroy = function () {
  return this.res.destroy.apply(this.res, arguments);
};

Message.prototype.pause = function () {
  return this.res.pause.apply(this.res, arguments);
};

Message.prototype.resume = function () {
  return this.res.resume.apply(this.res, arguments);
};

// --- Message Parsers ---

mime.define({
  'application/vnd.citationstyles.csl+json': ['json']
});

/**
 * The available Zotero API response body parsers.
 *
 * @property parsers
 * @type Object
 * @static
 */
Message.parsers = {
  json: function (data, callback) {
    try {
      var parsed = JSON.parse(data.toString(this.encoding));

    } catch (error) {
      return callback(error, data);
    }

    return callback(null, parsed);

  },

  txt: function (data, callback) {
    try {
      var parsed = data.toString(this.encoding);

    } catch (error) {
      return callback(error, data);
    }

    return callback(null, parsed);
  }
};

Message.parsers.html = Message.parsers.txt;

// --- Helpers ---

function h(options) {
  if ((/^https/i).test(options.protocol)) return https;
  if (options.port === 443) return https;

  return http;
}

function property(obj, name, descriptor) {
  Object.defineProperty(obj, name, descriptor);
}

function reader(obj, name, value) {
  var descriptor = { configurable: true, enumerable: false };

  if (typeof value === 'function') {
    descriptor.get = value;
  } else {
    descriptor.value = value;
  }

  property(obj, name, descriptor);
}

function Link(string) {
  var p = url.parse(string);

  this.path = p.pathname;

  if (p.query)
    this.options = qs.parse(p.query);
}

Link.parse = function (text) {
  var links = {};

  if (text) {
    debug('parsing link header...');

    // Link headers look like this (comma separated):
    // <https://api.zotero.org/users/12345/items?limit=30&start=30>; rel="next"
    var pattern = /<([^>]+)>;\s*rel="([^"]+)"/g;
    var m;

    while ((m = pattern.exec(text)) !== null)
      links[m[2]] = new Link(m[1]);

  }

  return links;
};

reader(Link.prototype, 'url', function () {
  var query = qs.stringify(this.options);
  if (query.length) query = '?' + query;

  return this.path + query;
});

// --- Exports ---
exports = module.exports = Message;
exports.Link = Link;
<|endoftext|>


<commit_before>var assert = require('assert'),
    cp = require('child_process'),
    path = require('path'),
    async = require('async');

function read_all(s, cb)
{
    var bufs = [];

    s.on('end', function ()
    {
        if (cb)
        {
            cb(Buffer.concat(bufs));
        }
    });

    s.on('readable', function ()
    {
        while (true)
        {
            var data = this.read();
            if (data === null) { break; }
            bufs.push(data);
        }
    });
}

function drain(p, cb)
{
    var stdout, stderr;

    read_all(p.stdout, function (v)
    {
        stdout = v;
    });
    read_all(p.stderr, function (v)
    {
        stderr = v;
    });

    p.on('close', function (code)
    {
        cb(code, stdout, stderr);
    });
}

describe('example', function ()
{
    this.timeout(60000);

    it('subscribers should connect, receive then disconnect', function (done)
    {
        var count = 0,
            servers = [],
            base_port = 8700;

        async.times(2, function (n, next)
        {
            var p = cp.fork(path.join(__dirname, 'server.js'), [base_port + n], { silent: true });

            drain(p, function (code, stdout, stderr)
            {
                assert.equal(code, 0);
                assert.equal(stdout.length, 0);
                assert.equal(stderr.length, 0);
            });

            p.on('message', function (m)
            {
                var exits = 0;
                function exit()
                {
                    exits += 1;
                    if (exits === servers.length)
                    {
                        done();
                    }
                }

                switch (m)
                {
                    case 'listening':
                        next(null, this);
                        break;

                    case 'connect':
                        count += 1;
                        break;

                    case 'disconnect':
                        count -= 1;
                        if (count === 0)
                        {
                            for (var server of servers)
                            {
                                server.on('close', exit);
                                server.send('stop');
                            }
                        }
                        break;
                }
            });
        }, function (err, svrs)
        {
            servers = svrs;

            var p = cp.fork(path.join(__dirname, 'client_subscribe.js'), [base_port, 'foo.bar'], { silent: true });

            drain(p, function (code, stdout, stderr)
            {
                assert.equal(code, 0);
                assert.equal(stdout.toString(), 'received foo.bar hello\n');
                assert.equal(stderr.length, 0);
            });

            p.on('message', function ()
            {
                p = cp.fork(path.join(__dirname, 'client_subscribe.js'), [base_port + 1, 'foo.*'], { silent: true});

                drain(p, function (code, stdout, stderr)
                {
                    assert.equal(code, 0);
                    assert.equal(stdout.toString(), 'received foo.bar hello\n');
                    assert.equal(stderr.length, 0);
                });

                p.on('message', function ()
                {
                    cp.fork(path.join(__dirname, 'client_publish.js'), [base_port, 'foo.bar']);
                });
            });
        });
    });
});
<commit_msg>Fix test under PG<commit_after>var assert = require('assert'),
    cp = require('child_process'),
    path = require('path'),
    async = require('async');

function read_all(s, cb)
{
    var bufs = [];

    s.on('end', function ()
    {
        if (cb)
        {
            cb(Buffer.concat(bufs));
        }
    });

    s.on('readable', function ()
    {
        while (true)
        {
            var data = this.read();
            if (data === null) { break; }
            bufs.push(data);
        }
    });
}

function drain(p, cb)
{
    var stdout, stderr;

    read_all(p.stdout, function (v)
    {
        stdout = v;
    });
    read_all(p.stderr, function (v)
    {
        stderr = v;
    });

    p.on('close', function (code)
    {
        cb(code, stdout, stderr);
    });
}

describe('example', function ()
{
    this.timeout(60000);

    it('subscribers should connect, receive then disconnect', function (done)
    {
        var count = 0,
            servers = [],
            base_port = 8700;

        async.times(2, function (n, next)
        {
            var p = cp.fork(path.join(__dirname, 'server.js'), [base_port + n], { silent: true });

            drain(p, function (code, stdout, stderr)
            {
                var s, pos1, pos2, pos3;
                assert.equal(code, 0);
                assert.equal(stdout.length, 0);
                if (process.env.USE_QLOBBER_PG === '1')
                {
                    s = stderr.toString();
                    pos1 = s.indexOf('Error: stopped');
                    pos2 = s.indexOf('Error: Connection terminated');
                    pos3 = s.indexOf('Error: Client was closed and is not queryable');
                    assert((stderr.length === 0) || (pos1 === 0) || (pos2 === 0) || (pos3 === 0));
                }
                else
                {
                    assert.equal(stderr.length, 0);
                }
            });

            p.on('message', function (m)
            {
                var exits = 0;
                function exit()
                {
                    exits += 1;
                    if (exits === servers.length)
                    {
                        done();
                    }
                }

                switch (m)
                {
                    case 'listening':
                        next(null, this);
                        break;

                    case 'connect':
                        count += 1;
                        break;

                    case 'disconnect':
                        count -= 1;
                        if (count === 0)
                        {
                            for (var server of servers)
                            {
                                server.on('close', exit);
                                server.send('stop');
                            }
                        }
                        break;
                }
            });
        }, function (err, svrs)
        {
            servers = svrs;

            var p = cp.fork(path.join(__dirname, 'client_subscribe.js'), [base_port, 'foo.bar'], { silent: true });

            drain(p, function (code, stdout, stderr)
            {
                assert.equal(code, 0);
                assert.equal(stdout.toString(), 'received foo.bar hello\n');
                assert.equal(stderr.length, 0);
            });

            p.on('message', function ()
            {
                p = cp.fork(path.join(__dirname, 'client_subscribe.js'), [base_port + 1, 'foo.*'], { silent: true});

                drain(p, function (code, stdout, stderr)
                {
                    assert.equal(code, 0);
                    assert.equal(stdout.toString(), 'received foo.bar hello\n');
                    assert.equal(stderr.length, 0);
                });

                p.on('message', function ()
                {
                    cp.fork(path.join(__dirname, 'client_publish.js'), [base_port, 'foo.bar']);
                });
            });
        });
    });
});
<|endoftext|>


<commit_before>// Karma configuration
// http://karma-runner.github.io/0.10/config/configuration-file.html

module.exports = function(config) {
  config.set({
    // base path, that will be used to resolve files and exclude
    basePath: '',

    // testing framework to use (jasmine/mocha/qunit/...)
    frameworks: ['jasmine'],

    // list of files / patterns to load in the browser
    files: [
      'app/bower_components/angular/angular.js',
      'app/bower_components/angular-mocks/angular-mocks.js',
      'app/bower_components/angular-resource/angular-resource.js',
      'app/scripts/*.js',
      'app/scripts/**/*.js',
      'test/mock/**/*.js',
      'test/spec/**/*.js'
    ],

    // list of files / patterns to exclude
    exclude: [],

    // web server port
    port: 8080,

    // level of logging
    // possible values: LOG_DISABLE || LOG_ERROR || LOG_WARN || LOG_INFO || LOG_DEBUG
    logLevel: config.LOG_INFO,

    // enable / disable watching file and executing tests whenever any file changes
    autoWatch: false,

    // Start these browsers, currently available:
    // - Chrome
    // - ChromeCanary
    // - Firefox
    // - Opera
    // - Safari (only Mac)
    // - PhantomJS
    // - IE (only Windows)
    browsers: ['Chrome'],

    // Continuous Integration mode
    // if true, it capture browsers, run tests and exit
    singleRun: false
  });
};
<commit_msg>Firefox is available in Travis-ci, Chrome is not.<commit_after>// Karma configuration
// http://karma-runner.github.io/0.10/config/configuration-file.html

module.exports = function(config) {
  config.set({
    // base path, that will be used to resolve files and exclude
    basePath: '',

    // testing framework to use (jasmine/mocha/qunit/...)
    frameworks: ['jasmine'],

    // list of files / patterns to load in the browser
    files: [
      'app/bower_components/angular/angular.js',
      'app/bower_components/angular-mocks/angular-mocks.js',
      'app/bower_components/angular-resource/angular-resource.js',
      'app/scripts/*.js',
      'app/scripts/**/*.js',
      'test/mock/**/*.js',
      'test/spec/**/*.js'
    ],

    // list of files / patterns to exclude
    exclude: [],

    // web server port
    port: 8080,

    // level of logging
    // possible values: LOG_DISABLE || LOG_ERROR || LOG_WARN || LOG_INFO || LOG_DEBUG
    logLevel: config.LOG_INFO,

    // enable / disable watching file and executing tests whenever any file changes
    autoWatch: false,

    // Start these browsers, currently available:
    // - Chrome
    // - ChromeCanary
    // - Firefox
    // - Opera
    // - Safari (only Mac)
    // - PhantomJS
    // - IE (only Windows)
    browsers: ['Firefox'],

    // Continuous Integration mode
    // if true, it capture browsers, run tests and exit
    singleRun: false
  });
};
<|endoftext|>


<commit_before>(function() {
/**
 * PLUTO
 * Native HTML Template Rendering
 *
 *
 * The MIT License (MIT)
 * Copyright (c) 2013 Roi Avinoam
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
class Template extends HTMLTemplateElement {
    render(obj) {
        var { content, exprs, items } = this.compile()
        return new Renderer(content, exprs, items).render(obj)
    }

    _renderIn(obj, el) {
        if (el.__plutoRenderer) {
            el.__plutoRenderer.render(obj)
        } else {
            var doc = this.render(obj)
            el.replaceWith(doc)
            el.__plutoRenderer = doc
            el.remove = doc.remove.bind(doc)
        }
    }

    compile() {
        var exprs = []
        var clone = this.cloneNode(true)
        var elements = [{ el: clone.content, path: [] }]
        while (elements.length > 0) {
            var { el, path } = elements.shift()

            if (el.localName === 'template') {
                // nested templates are coerced to Pluto Templates. This
                // prevents the need to repeatedly mark templates with pluto-tpl
                // with the trade-off of not supporting a mixture of template
                // libraries.
                var render = (el, _, obj) => pluto(el)._renderIn(obj, el)
                exprs.push({ path, render })
                continue
            }

            // inner content expressions
            if (el.nodeName === '#text') {
                var expr = isExpressions(el.textContent)
                if (expr) {
                    el.textContent = ''
                    var render = (el, v) => (el.textContent = v || '')
                    exprs.push({ expr, path, render })
                }
            }

            // attributes
            Array.from(el.attributes || []).forEach(function(attr) {
                var expr = isExpressions(attr.value)
                if (expr === null) {
                    return
                }

                // avoid having the attribute on import as it might arrive
                // un-rendered to web-components. NB: this means that the
                // constructor of elements wouldn't have access to the rendered
                // values initially. Perhaps we should initially render into a
                // clone before finally importing.
                el.removeAttribute(attr.name)
                attr = attr.name
                var evName, prop
                if (attr.startsWith('on-')) {
                    evName = attr.slice(3)
                    var render = function(el, v) {
                        var evs = el.__plutoEvs || (el.__plutoEvs = {})
                        if (evs[evName] !== v) {
                            el.removeEventListener(evName, evs[evName])
                            el.addEventListener(evName, evs[evName] = v)
                        }
                    }
                } else if (['style', 'class'].indexOf(attr) !== -1) {
                    var render = (el, v) => v
                        ? el.setAttribute(attr, v)
                        : el.removeAttribute(attr)
                } else {
                    prop = snakeToCamelCase(attr)
                    var render = (el, v) => el[prop] = v
                }

                exprs.push({ expr, path, attr, evName, prop, render })
            }, this)

            // enqueue children
            el.childNodes.forEach(function(el, i) {
                elements.push({ el, path: path.concat(['childNodes', i]) })
            })
        }

        var repeat = this.getAttribute('repeat') || this.getAttribute('for')
        if (repeat) {
            repeat = compileExpressions([{ expr: repeat }])
            clone.items = (obj) => repeat(obj)[0] || []
        }

        var elseIf = this.getAttribute('else-if')
        var cond = this.getAttribute('if') || elseIf
        if (cond) {
            cond = compileExpressions([{ expr: cond }])
            clone.items = (obj) => Boolean(cond(obj)[0])
        }

        var else_ = this.hasAttribute('else') || elseIf
        if (else_) {
            var else_ = clone.items || Array
            clone.items = (obj) => obj.__plutoElse ? [] : else_(obj)
        }

        // we opt to compile the repeat/cond expressions separately than the
        // rest of this template - because (a) the template might relay on a
        // repeated ${item} property that doesn't yet exist in the repeat
        // expression, and (b) it's must smaller/faster than the complete
        // expressions list.
        // NB: It might not be that beneficial for cond though.
        clone.exprs = Object.assign(exprs, { eval: compileExpressions(exprs) })
        return clone
    }
}

class Renderer extends DocumentFragment {
    constructor(content, exprs, items) {
        super()
        this.tpl = content
        this.exprs = exprs
        this.items = items

        this.elements = []
        this.paths = {}

        if (items) {
            this.placeholder = document.createTextNode('')
            this.appendChild(this.placeholder)
        } else {
            this.appendChild(document.importNode(this.tpl, true))

            // copy the list of generated elements from the template in order
            // to support removals
            this.elements = [].map.call(this.childNodes, child => child)
            this.paths = this.exprs.map((expr) => select(this, expr.path))
        }
    }

    remove() {
        while (this.elements.length > 0) {
            this.elements.pop().remove()
        }
    }

    render(obj) {
        if (!this.items) {
            return this._renderOne(obj)
        }

        var items = this.items(obj)
        var item = obj.item
        if (!Array.isArray(items) && typeof items === 'object') {
            items = Object.keys(items).map(function(k) {
                return { key: k, value: items[k] }
            })
        }

        if (typeof items === 'boolean') {
            items = Number(items) // 0 or 1
        }

        if (typeof items === 'number') {
            items = new Array(items) // range-items, repeat N times.
            items = Array.from(items).map(() => item)
        }

        obj.__plutoElse = obj.__plutoElse || items && items.length > 0

        // remove obsolete items
        while (this.elements.length > items.length) {
            this.elements.pop().remove()
        }

        // update existing items
        for (var i = 0; i < this.elements.length; i += 1) {
            obj.item = items[i]
            this.elements[i].render(obj)
        }

        // create new items
        while (this.elements.length < items.length) {
            var i = this.elements.length
            obj.item = items[i]
            var doc = new Renderer(this.tpl, this.exprs).render(obj)
            this.elements.push(doc)
            this.placeholder.before(doc)
        }

        obj.item = item // restore previous item value.
        return this
    }

    _renderOne(obj) {
        var else_ = obj.__plutoElse
        obj.__plutoElse = false

        var values = this.exprs.eval(obj)
        var subtpls = []
        for (var i = 0 ; i < this.exprs.length ; i += 1) {
            var expr = this.exprs[i]
            var el = this.paths[i]
            var v = values[i]
            expr.render(el, v, obj)
        }

        obj.__plutoElse = else_
        return this
    }
}

// -- HELPER FUNCTIONS

// Searches for an element from root based on the property-path to the child
// example: root = <body>, path = childNodes.3.childNode.7. Resolved by walking
// the path down to the child.
function select(root, path) {
    var el = root
    for (var i = 0; el !== undefined && i < path.length; i += 1) {
        el = el[path[i]]
    }
    return el
}

const SNAKE_RE = /-([a-z])/g
function snakeToCamelCase(s) {
    return s.replace(SNAKE_RE, g => g[1].toUpperCase())
}

// extract expressions in template-literal syntax out of a string
var EXPR_RE = /\$\{[^\}]*\}/
function isExpressions(s) {
    return EXPR_RE.test(s) ? s : null
}

// compile a list of template-literal expressions into a function that evaluates
// these expression for the provided input object
function compileExpressions(exprs) {
    var refs = []
    var code = 'this.__plutoT || (this.__plutoT = T.bind(this));\n'
    code += exprs.map(function(expr, i) {
        if (expr.expr) {
            refs = refs.concat(getIdentifiers(expr.expr))
        }

        var tagFn = ({
            'class': 'classNameT',
            'style': 'styleT'
        })[expr.attr] || 'this.__plutoT'

        return `arguments[0][${i}] = ${tagFn}\`${expr.expr}\``
    }).join(';\n')

    // define the local variables and bind root-level functions to the provided
    // rendered object.
    var keys = refs.reduce((keys, k) => (keys[k] = true, keys), {})
    var locals = Object.keys(keys).map(function (k) {
        return `
            var ${k} = this["${k}"];
            typeof ${k} === 'function' && (${k} = bindFn(this, ${k}))
        `
    }).join(';\n')

    var fn = eval('(function () {\n' + locals + '\n' + code + '\n})')
    return function(obj) {
        var res = []
        try {
            fn.call(obj, res)
        } catch (e) {
            console.warn(fn)
            throw e
        }

        return res
    }

    function bindFn(obj, fn) {
        var bound = fn.__plutoBound || (fn.__plutoBound = {})
        if (bound.to !== obj) {
            bound.to = obj
            bound.fn = fn.bind(obj)
        }

        return bound.fn
    }

    function T(s, v) {
        return arguments.length > 2 || typeof v === 'string'
            ? String.raw.apply(null, arguments)
            : v
    }

    function classNameT(s, v) {
        if (typeof v === 'object') {
            if (!Array.isArray(v)) {
                v = Object.keys(v).filter((k) => v[k])
            }

            return v.join(' ')
        }

        return String.raw.apply(null, arguments)
    }

    function styleT(s, v) {
        if (typeof v === 'object') {
            return Object.keys(v).map((k) => k + ':' + v[k]).join('; ')
        }

        return String.raw.apply(null, arguments)
    }
}

// generate the list of identifiers found in the code.
function getIdentifiers(expr) {
    var re = /[$A-Z_][0-9A-Z_$]*/ig
    var whitespace = ' \n\r\t'
    var disallowed = '\'\".'
    var skip = ['true', 'false', 'if', 'for', 'while', 'do', 'try', 'catch',
        'break', 'continue', 'switch', 'throw', 'this', 'instanceof', 'in',
        'function', 'delete', 'default', 'case', 'debugger', 'const', 'var',
        'with', 'typeof', 'super', 'class', 'new', 'null', 'return', 'let',
        'import', 'else', 'enum', 'extends', 'finally', '$']

    // We first match for the valid identifier, and then check the previous
    // non-whitespace character preceeding the identifier to verify that it's
    // not a string or nested element.
    var refs = {}
    var match
    while (match = re.exec(expr)) {
        if (skip.indexOf(match[0]) !== -1) {
            continue // skipped or reserved keyword
        }

        if (window[match[0]] !== undefined) {
            continue // keep global functions (Object, Array, etc.)
        }

        var lastChar = undefined
        do {
            match.index -= 1
            if (whitespace.indexOf(expr[match.index]) === -1) {
                lastChar = expr[match.index]
            }
        } while (match.index > -1 && !lastChar)

        if (disallowed.indexOf(lastChar) === -1) {
            refs[match[0]] = true
        }
    }

    return Object.keys(refs)
}

// upgrade an element
// NOTE this will be unnecessary with customElements
function pluto(el) {
    if (typeof el === 'string') {
        // support HTML-imported selections
        var doc = document._currentScript // used by pollyfills
            || document.currentScript // native.
            || document // not an import.
        el = (doc.ownerDocument || doc).querySelector(el)
    }

    return !el || el instanceof Template
        ? el
        : Object.setPrototypeOf(el, Template.prototype)
}

pluto.Template = Template
pluto.RepeatRenderer = Renderer
window.pluto = pluto
})();

(function() {

    // placed here in order to have its own scope clear of any of the pluto
    // local variables.
    pluto._eval = function(code) {
        return eval(code)
    }
})();
<commit_msg>Skip undefined expressions. Can happen in nested templates<commit_after>(function() {
/**
 * PLUTO
 * Native HTML Template Rendering
 *
 *
 * The MIT License (MIT)
 * Copyright (c) 2013 Roi Avinoam
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
class Template extends HTMLTemplateElement {
    render(obj) {
        var { content, exprs, items } = this.compile()
        return new Renderer(content, exprs, items).render(obj)
    }

    _renderIn(obj, el) {
        if (el.__plutoRenderer) {
            el.__plutoRenderer.render(obj)
        } else {
            var doc = this.render(obj)
            el.replaceWith(doc)
            el.__plutoRenderer = doc
            el.remove = doc.remove.bind(doc)
        }
    }

    compile() {
        var exprs = []
        var clone = this.cloneNode(true)
        var elements = [{ el: clone.content, path: [] }]
        while (elements.length > 0) {
            var { el, path } = elements.shift()

            if (el.localName === 'template') {
                // nested templates are coerced to Pluto Templates. This
                // prevents the need to repeatedly mark templates with pluto-tpl
                // with the trade-off of not supporting a mixture of template
                // libraries.
                var render = (el, _, obj) => pluto(el)._renderIn(obj, el)
                exprs.push({ path, render })
                continue
            }

            // inner content expressions
            if (el.nodeName === '#text') {
                var expr = isExpressions(el.textContent)
                if (expr) {
                    el.textContent = ''
                    var render = (el, v) => (el.textContent = v || '')
                    exprs.push({ expr, path, render })
                }
            }

            // attributes
            Array.from(el.attributes || []).forEach(function(attr) {
                var expr = isExpressions(attr.value)
                if (expr === null) {
                    return
                }

                // avoid having the attribute on import as it might arrive
                // un-rendered to web-components. NB: this means that the
                // constructor of elements wouldn't have access to the rendered
                // values initially. Perhaps we should initially render into a
                // clone before finally importing.
                el.removeAttribute(attr.name)
                attr = attr.name
                var evName, prop
                if (attr.startsWith('on-')) {
                    evName = attr.slice(3)
                    var render = function(el, v) {
                        var evs = el.__plutoEvs || (el.__plutoEvs = {})
                        if (evs[evName] !== v) {
                            el.removeEventListener(evName, evs[evName])
                            el.addEventListener(evName, evs[evName] = v)
                        }
                    }
                } else if (['style', 'class'].indexOf(attr) !== -1) {
                    var render = (el, v) => v
                        ? el.setAttribute(attr, v)
                        : el.removeAttribute(attr)
                } else {
                    prop = snakeToCamelCase(attr)
                    var render = (el, v) => el[prop] = v
                }

                exprs.push({ expr, path, attr, evName, prop, render })
            }, this)

            // enqueue children
            el.childNodes.forEach(function(el, i) {
                elements.push({ el, path: path.concat(['childNodes', i]) })
            })
        }

        var repeat = this.getAttribute('repeat') || this.getAttribute('for')
        if (repeat) {
            repeat = compileExpressions([{ expr: repeat }])
            clone.items = (obj) => repeat(obj)[0] || []
        }

        var elseIf = this.getAttribute('else-if')
        var cond = this.getAttribute('if') || elseIf
        if (cond) {
            cond = compileExpressions([{ expr: cond }])
            clone.items = (obj) => Boolean(cond(obj)[0])
        }

        var else_ = this.hasAttribute('else') || elseIf
        if (else_) {
            var else_ = clone.items || Array
            clone.items = (obj) => obj.__plutoElse ? [] : else_(obj)
        }

        // we opt to compile the repeat/cond expressions separately than the
        // rest of this template - because (a) the template might relay on a
        // repeated ${item} property that doesn't yet exist in the repeat
        // expression, and (b) it's must smaller/faster than the complete
        // expressions list.
        // NB: It might not be that beneficial for cond though.
        clone.exprs = Object.assign(exprs, { eval: compileExpressions(exprs) })
        return clone
    }
}

class Renderer extends DocumentFragment {
    constructor(content, exprs, items) {
        super()
        this.tpl = content
        this.exprs = exprs
        this.items = items

        this.elements = []
        this.paths = {}

        if (items) {
            this.placeholder = document.createTextNode('')
            this.appendChild(this.placeholder)
        } else {
            this.appendChild(document.importNode(this.tpl, true))

            // copy the list of generated elements from the template in order
            // to support removals
            this.elements = [].map.call(this.childNodes, child => child)
            this.paths = this.exprs.map((expr) => select(this, expr.path))
        }
    }

    remove() {
        while (this.elements.length > 0) {
            this.elements.pop().remove()
        }
    }

    render(obj) {
        if (!this.items) {
            return this._renderOne(obj)
        }

        var items = this.items(obj)
        var item = obj.item
        if (!Array.isArray(items) && typeof items === 'object') {
            items = Object.keys(items).map(function(k) {
                return { key: k, value: items[k] }
            })
        }

        if (typeof items === 'boolean') {
            items = Number(items) // 0 or 1
        }

        if (typeof items === 'number') {
            items = new Array(items) // range-items, repeat N times.
            items = Array.from(items).map(() => item)
        }

        obj.__plutoElse = obj.__plutoElse || items && items.length > 0

        // remove obsolete items
        while (this.elements.length > items.length) {
            this.elements.pop().remove()
        }

        // update existing items
        for (var i = 0; i < this.elements.length; i += 1) {
            obj.item = items[i]
            this.elements[i].render(obj)
        }

        // create new items
        while (this.elements.length < items.length) {
            var i = this.elements.length
            obj.item = items[i]
            var doc = new Renderer(this.tpl, this.exprs).render(obj)
            this.elements.push(doc)
            this.placeholder.before(doc)
        }

        obj.item = item // restore previous item value.
        return this
    }

    _renderOne(obj) {
        var else_ = obj.__plutoElse
        obj.__plutoElse = false

        var values = this.exprs.eval(obj)
        var subtpls = []
        for (var i = 0 ; i < this.exprs.length ; i += 1) {
            var expr = this.exprs[i]
            var el = this.paths[i]
            var v = values[i]
            expr.render(el, v, obj)
        }

        obj.__plutoElse = else_
        return this
    }
}

// -- HELPER FUNCTIONS

// Searches for an element from root based on the property-path to the child
// example: root = <body>, path = childNodes.3.childNode.7. Resolved by walking
// the path down to the child.
function select(root, path) {
    var el = root
    for (var i = 0; el !== undefined && i < path.length; i += 1) {
        el = el[path[i]]
    }
    return el
}

const SNAKE_RE = /-([a-z])/g
function snakeToCamelCase(s) {
    return s.replace(SNAKE_RE, g => g[1].toUpperCase())
}

// extract expressions in template-literal syntax out of a string
var EXPR_RE = /\$\{[^\}]*\}/
function isExpressions(s) {
    return EXPR_RE.test(s) ? s : null
}

// compile a list of template-literal expressions into a function that evaluates
// these expression for the provided input object
function compileExpressions(exprs) {
    var refs = []
    var code = 'this.__plutoT || (this.__plutoT = T.bind(this));\n'
    code += exprs.map(function(expr, i) {
        if (expr.expr === undefined) {
            return '' // can happen in nested templates
        }

        if (expr.expr) {
            refs = refs.concat(getIdentifiers(expr.expr))
        }

        var tagFn = ({
            'class': 'classNameT',
            'style': 'styleT'
        })[expr.attr] || 'this.__plutoT'

        return `arguments[0][${i}] = ${tagFn}\`${expr.expr}\``
    }).join(';\n')

    // define the local variables and bind root-level functions to the provided
    // rendered object.
    var keys = refs.reduce((keys, k) => (keys[k] = true, keys), {})
    var locals = Object.keys(keys).map(function (k) {
        return `
            var ${k} = this["${k}"];
            typeof ${k} === 'function' && (${k} = bindFn(this, ${k}))
        `
    }).join(';\n')

    var fn = eval('(function () {\n' + locals + '\n' + code + '\n})')
    return function(obj) {
        var res = []
        try {
            fn.call(obj, res)
        } catch (e) {
            console.warn(fn)
            throw e
        }

        return res
    }

    function bindFn(obj, fn) {
        var bound = fn.__plutoBound || (fn.__plutoBound = {})
        if (bound.to !== obj) {
            bound.to = obj
            bound.fn = fn.bind(obj)
        }

        return bound.fn
    }

    function T(s, v) {
        return arguments.length > 2 || typeof v === 'string'
            ? String.raw.apply(null, arguments)
            : v
    }

    function classNameT(s, v) {
        if (typeof v === 'object') {
            if (!Array.isArray(v)) {
                v = Object.keys(v).filter((k) => v[k])
            }

            return v.join(' ')
        }

        return String.raw.apply(null, arguments)
    }

    function styleT(s, v) {
        if (typeof v === 'object') {
            return Object.keys(v).map((k) => k + ':' + v[k]).join('; ')
        }

        return String.raw.apply(null, arguments)
    }
}

// generate the list of identifiers found in the code.
function getIdentifiers(expr) {
    var re = /[$A-Z_][0-9A-Z_$]*/ig
    var whitespace = ' \n\r\t'
    var disallowed = '\'\".'
    var skip = ['true', 'false', 'if', 'for', 'while', 'do', 'try', 'catch',
        'break', 'continue', 'switch', 'throw', 'this', 'instanceof', 'in',
        'function', 'delete', 'default', 'case', 'debugger', 'const', 'var',
        'with', 'typeof', 'super', 'class', 'new', 'null', 'return', 'let',
        'import', 'else', 'enum', 'extends', 'finally', '$']

    // We first match for the valid identifier, and then check the previous
    // non-whitespace character preceeding the identifier to verify that it's
    // not a string or nested element.
    var refs = {}
    var match
    while (match = re.exec(expr)) {
        if (skip.indexOf(match[0]) !== -1) {
            continue // skipped or reserved keyword
        }

        if (window[match[0]] !== undefined) {
            continue // keep global functions (Object, Array, etc.)
        }

        var lastChar = undefined
        do {
            match.index -= 1
            if (whitespace.indexOf(expr[match.index]) === -1) {
                lastChar = expr[match.index]
            }
        } while (match.index > -1 && !lastChar)

        if (disallowed.indexOf(lastChar) === -1) {
            refs[match[0]] = true
        }
    }

    return Object.keys(refs)
}

// upgrade an element
// NOTE this will be unnecessary with customElements
function pluto(el) {
    if (typeof el === 'string') {
        // support HTML-imported selections
        var doc = document._currentScript // used by pollyfills
            || document.currentScript // native.
            || document // not an import.
        el = (doc.ownerDocument || doc).querySelector(el)
    }

    return !el || el instanceof Template
        ? el
        : Object.setPrototypeOf(el, Template.prototype)
}

pluto.Template = Template
pluto.RepeatRenderer = Renderer
window.pluto = pluto
})();

(function() {

    // placed here in order to have its own scope clear of any of the pluto
    // local variables.
    pluto._eval = function(code) {
        return eval(code)
    }
})();
<|endoftext|>


<commit_before>'use strict';

const
  fs = require('fs'),
  nurl = require('url'),
  path = require('path'),
  stream = require('stream'),
  request = require('request');

const REGEX = {
  url: /^https?\:\/\/|www\./
};

// Methods
const methods = {
  getMe: {},
  sendMessage: {
    arguments: ['chat_id', 'text']
  },
  forwardMessage: {
    arguments: ['chat_id', 'from_chat_id', 'message_id']
  },
  sendPhoto(id, photo, opt) {
    return sendFile.call(this, 'photo', id, photo, opt);
  },
  sendAudio(id, audio, opt) {
    return sendFile.call(this, 'audio', id, audio, opt);
  },
  sendDocument(id, doc, opt) {
    return sendFile.call(this, 'document', id, doc, opt);
  },
  sendSticker(id, sticker, opt) {
    return sendFile.call(this, 'sticker', id, sticker, opt);
  },
  sendVideo(id, video, opt) {
    return sendFile.call(this, 'video', id, video, opt);
  },
  sendVoice(id, voice, opt) {
    return sendFile.call(this, 'voice', id, voice, opt);
  },
  sendLocation: {
    arguments: (chat_id, position) => {
      return {
        chat_id, latitude: position[0], longitude: position[1]
      };
    }
  },
  sendVenue: {},
  sendContact: {},
  sendChatAction: {
    short: 'sendAction',
    arguments: ['chat_id', 'action']
  },
  getUserProfilePhotos: {
    short: 'getUserPhoto',
    arguments: 'chat_id',
    options: (form, opt) => {
      if (opt.offset) form.offset = opt.offset;
      if (opt.limit) form.limit = opt.limit;
      return form;
    }
  },
  getFile: {
    arguments: 'file_id',
    then: file => {
      const result = file.result;
      result.fileLink = this.fileLink + result.file_path;
      return result;
    }
  },
  kickChatMember: {
    short: 'kick',
    arguments: ['chat_id', 'user_id']
  },
  unbanChatMember: {
    short: 'unban',
    arguments: ['chat_id', 'user_id']
  },
  answerInlineQuery: {
    short: 'answerQuery',
    arguments: answers => {
      return {
        inline_query_id: answers.id,
        results: answers.results(),
        next_offset: answers.nextOffset,
        is_personal: answers.personal,
        cache_time: answers.cacheTime
      };
    }
  },
  editMessageText: {
    short: 'editMessage',
    arguments: (obj, text) => editObject(obj, { text })
  },
  editMessageCaption: {
    short: 'editCaption',
    arguments: (obj, caption) => editObject(obj, { caption })
  },
  editMessageReplyMarkup: {
    short: 'editMarkup',
    arguments: (obj, reply_markup) => editObject(obj, { reply_markup })
  },
  setWebhook: {
    arguments: ['url', 'certificate']
  }
};

// Functions

function editObject(obj, form) {
  if (obj.chat && obj.message) {
    form.chat_id = obj.chat;
    form.message_id = obj.message;
  } else if (obj.inline) {
    form.inline_message_id = obj.inline;
  }
  return form;
}

function sendFile(type, chat_id, file, opt) {
  opt = opt || {};
  const form = this.properties({ chat_id }, opt);
  let url = 'send' + type.charAt(0).toUpperCase() + type.slice(1);
  // Send bot action event
  console.log('.', type, form, file);
  this.event(url, [].slice.call(arguments).splice(0, 1));
  // Set file caption
  if (opt.caption) form.caption = opt.caption;
  url = '/' + url;
  if (file instanceof stream.Stream) {
    // File stream object
    if (!opt.fileName)
      opt.fileName = nurl.parse(path.basename(file.path)).pathname;
    form[type] = {
      value: file,
      options: { filename: opt.fileName }
    };
  } else if (Buffer.isBuffer(file)) {
    // File buffer
    if (!opt.fileName) opt.fileName = 'blob_file';
    form[type] = {
      value: file,
      options: { filename: opt.fileName }
    };
  } else if (REGEX.url.test(file)) {
    // File url
    if (!opt.fileName)
      opt.fileName = path.basename(nurl.parse(file).pathname) || 'file';
    form[type] = {
      value: request.get(file),
      options: { filename: opt.fileName }
    };
  } else if (fs.existsSync(file)) {
    // File location
    if (!opt.fileName) opt.fileName = path.basename(file);
    form[type] = {
      value: fs.createReadStream(file),
      options: { filename: opt.fileName }
    };
  } else {
    // File as 'file_id'
    form[type] = file;
  }
  return this.request(url, null, form);
}

// Export methods
module.exports = methods;
<commit_msg>Remove debug line<commit_after>'use strict';

const
  fs = require('fs'),
  nurl = require('url'),
  path = require('path'),
  stream = require('stream'),
  request = require('request');

const REGEX = {
  url: /^https?\:\/\/|www\./
};

// Methods
const methods = {
  getMe: {},
  sendMessage: {
    arguments: ['chat_id', 'text']
  },
  forwardMessage: {
    arguments: ['chat_id', 'from_chat_id', 'message_id']
  },
  sendPhoto(id, photo, opt) {
    return sendFile.call(this, 'photo', id, photo, opt);
  },
  sendAudio(id, audio, opt) {
    return sendFile.call(this, 'audio', id, audio, opt);
  },
  sendDocument(id, doc, opt) {
    return sendFile.call(this, 'document', id, doc, opt);
  },
  sendSticker(id, sticker, opt) {
    return sendFile.call(this, 'sticker', id, sticker, opt);
  },
  sendVideo(id, video, opt) {
    return sendFile.call(this, 'video', id, video, opt);
  },
  sendVoice(id, voice, opt) {
    return sendFile.call(this, 'voice', id, voice, opt);
  },
  sendLocation: {
    arguments: (chat_id, position) => {
      return {
        chat_id, latitude: position[0], longitude: position[1]
      };
    }
  },
  sendVenue: {},
  sendContact: {},
  sendChatAction: {
    short: 'sendAction',
    arguments: ['chat_id', 'action']
  },
  getUserProfilePhotos: {
    short: 'getUserPhoto',
    arguments: 'chat_id',
    options: (form, opt) => {
      if (opt.offset) form.offset = opt.offset;
      if (opt.limit) form.limit = opt.limit;
      return form;
    }
  },
  getFile: {
    arguments: 'file_id',
    then: file => {
      const result = file.result;
      result.fileLink = this.fileLink + result.file_path;
      return result;
    }
  },
  kickChatMember: {
    short: 'kick',
    arguments: ['chat_id', 'user_id']
  },
  unbanChatMember: {
    short: 'unban',
    arguments: ['chat_id', 'user_id']
  },
  answerInlineQuery: {
    short: 'answerQuery',
    arguments: answers => {
      return {
        inline_query_id: answers.id,
        results: answers.results(),
        next_offset: answers.nextOffset,
        is_personal: answers.personal,
        cache_time: answers.cacheTime
      };
    }
  },
  editMessageText: {
    short: 'editMessage',
    arguments: (obj, text) => editObject(obj, { text })
  },
  editMessageCaption: {
    short: 'editCaption',
    arguments: (obj, caption) => editObject(obj, { caption })
  },
  editMessageReplyMarkup: {
    short: 'editMarkup',
    arguments: (obj, reply_markup) => editObject(obj, { reply_markup })
  },
  setWebhook: {
    arguments: ['url', 'certificate']
  }
};

// Functions

function editObject(obj, form) {
  if (obj.chat && obj.message) {
    form.chat_id = obj.chat;
    form.message_id = obj.message;
  } else if (obj.inline) {
    form.inline_message_id = obj.inline;
  }
  return form;
}

function sendFile(type, chat_id, file, opt) {
  opt = opt || {};
  const form = this.properties({ chat_id }, opt);
  let url = 'send' + type.charAt(0).toUpperCase() + type.slice(1);
  // Send bot action event
  this.event(url, [].slice.call(arguments).splice(0, 1));
  // Set file caption
  if (opt.caption) form.caption = opt.caption;
  url = '/' + url;
  if (file instanceof stream.Stream) {
    // File stream object
    if (!opt.fileName)
      opt.fileName = nurl.parse(path.basename(file.path)).pathname;
    form[type] = {
      value: file,
      options: { filename: opt.fileName }
    };
  } else if (Buffer.isBuffer(file)) {
    // File buffer
    if (!opt.fileName) opt.fileName = 'blob_file';
    form[type] = {
      value: file,
      options: { filename: opt.fileName }
    };
  } else if (REGEX.url.test(file)) {
    // File url
    if (!opt.fileName)
      opt.fileName = path.basename(nurl.parse(file).pathname) || 'file';
    form[type] = {
      value: request.get(file),
      options: { filename: opt.fileName }
    };
  } else if (fs.existsSync(file)) {
    // File location
    if (!opt.fileName) opt.fileName = path.basename(file);
    form[type] = {
      value: fs.createReadStream(file),
      options: { filename: opt.fileName }
    };
  } else {
    // File as 'file_id'
    form[type] = file;
  }
  return this.request(url, null, form);
}

// Export methods
module.exports = methods;
<|endoftext|>