<commit_before>
import chai from 'chai';
import sinon from 'sinon';

import sync from '..';

const { expect } = chai;

describe('sync', () => {
  it('should return an animation obj', () => {
    const animation = sync('test');

    expect(animation).to.have.property('free');
    expect(animation).to.have.property('pause');
    expect(animation).to.have.property('stop');
    expect(animation).to.have.property('start');
  });

  it('should sync animations with the same name', () => {
    const animation = sync('test1');
    const testDiv1 = document.getElementById('test-1');
    const testDiv2 = document.getElementById('test-2');

    const animationEvent1 = Object.assign(new window.Event('animationstart', { bubbles: true }), { animationName: 'test1' });
    const animationEvent2 = Object.assign(new window.Event('animationstart', { bubbles: true }), { animationName: 'test1' });

    testDiv1.dispatchEvent(animationEvent1);
    expect(animation.getElements().size).to.equal(1);

    testDiv2.dispatchEvent(animationEvent2);
    expect(animation.getElements().size).to.equal(2);
  });

  it('should sync animations with differnt names', () => {
    const animation = sync(['test1', 'test2']);
    const testDiv1 = document.getElementById('test-1');
    const testDiv2 = document.getElementById('test-2');

    const animationEvent1 = Object.assign(new window.Event('animationstart', { bubbles: true }), { animationName: 'test1' });
    const animationEvent2 = Object.assign(new window.Event('animationstart', { bubbles: true }), { animationName: 'test2' });

    testDiv1.dispatchEvent(animationEvent1);
    expect(animation.getElements().size).to.equal(1);

    testDiv2.dispatchEvent(animationEvent2);
    expect(animation.getElements().size).to.equal(2);
  });

  it('should setup event listeners', () => {
    sinon.spy(window, 'addEventListener');

    sync('test');

    expect(window.addEventListener.getCall(0).args[0]).to.equal('animationstart');
    expect(window.addEventListener.getCall(1).args[0]).to.equal('animationiteration');
  });

  it('should free event listeners', () => {
    sinon.spy(window, 'removeEventListener');

    const animation = sync('test');
    animation.free();

    expect(window.removeEventListener.getCall(0).args[0]).to.equal('animationstart');
    expect(window.removeEventListener.getCall(1).args[0]).to.equal('animationiteration');
  });
});
<commit_msg>remove spacing<commit_after>
import chai from 'chai';
import sinon from 'sinon';

import sync from '..';

const { expect } = chai;

describe('sync', () => {
  it('should return an animation obj', () => {
    const animation = sync('test');

    expect(animation).to.have.property('free');
    expect(animation).to.have.property('pause');
    expect(animation).to.have.property('stop');
    expect(animation).to.have.property('start');
  });

  it('should sync animations with the same name', () => {
    const animation = sync('test1');
    const testDiv1 = document.getElementById('test-1');
    const testDiv2 = document.getElementById('test-2');

    const animationEvent1 = Object.assign(new window.Event('animationstart', { bubbles: true }), { animationName: 'test1' });
    const animationEvent2 = Object.assign(new window.Event('animationstart', { bubbles: true }), { animationName: 'test1' });

    testDiv1.dispatchEvent(animationEvent1);
    expect(animation.getElements().size).to.equal(1);

    testDiv2.dispatchEvent(animationEvent2);
    expect(animation.getElements().size).to.equal(2);
  });

  it('should sync animations with differnt names', () => {
    const animation = sync(['test1', 'test2']);
    const testDiv1 = document.getElementById('test-1');
    const testDiv2 = document.getElementById('test-2');

    const animationEvent1 = Object.assign(new window.Event('animationstart', { bubbles: true }), { animationName: 'test1' });
    const animationEvent2 = Object.assign(new window.Event('animationstart', { bubbles: true }), { animationName: 'test2' });

    testDiv1.dispatchEvent(animationEvent1);
    expect(animation.getElements().size).to.equal(1);

    testDiv2.dispatchEvent(animationEvent2);
    expect(animation.getElements().size).to.equal(2);
  });

  it('should setup event listeners', () => {
    sinon.spy(window, 'addEventListener');

    sync('test');

    expect(window.addEventListener.getCall(0).args[0]).to.equal('animationstart');
    expect(window.addEventListener.getCall(1).args[0]).to.equal('animationiteration');
  });

  it('should free event listeners', () => {
    sinon.spy(window, 'removeEventListener');

    const animation = sync('test');
    animation.free();

    expect(window.removeEventListener.getCall(0).args[0]).to.equal('animationstart');
    expect(window.removeEventListener.getCall(1).args[0]).to.equal('animationiteration');
  });
});
<|endoftext|>


<commit_before>/*
 * Copyright 2016 Teppo Kurki <teppo.kurki@iki.fi>
 *
 * Licensed 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.
 */

const Influx = require('influx')
const Bacon = require('baconjs')
const debug = require('debug')('signalk-to-influxdb')
const util = require('util')

module.exports = function (app) {
  let client
  let selfContext = 'vessels.' + app.selfId
  let lastPositionStored = 0

  let unsubscribes = []
  let shouldStore = function (path) {
    return true
  }

  function handleDelta (delta) {
    if (delta.context === 'vessels.self') {
      delta.context = selfContext
    }

    if (delta.updates && delta.context === selfContext) {
      delta.updates.forEach(update => {
        if (update.values) {
          var points = update.values.reduce((acc, pathValue) => {
            if (shouldStore(pathValue.path)) {
              if (typeof pathValue.value === 'number') {
                acc.push({
                  measurement: pathValue.path,
                  fields: {
                    value: pathValue.value
                  }
                })
              } else if (
                pathValue.path === 'navigation.position' &&
                new Date().getTime() - lastPositionStored > 1000
              ) {
                acc.push({
                  measurement: pathValue.path,
                  fields: {
                    value: JSON.stringify([
                      pathValue.value.longitude,
                      pathValue.value.latitude
                    ])
                  }
                })
                lastPositionStored = new Date().getTime()
              }
            }
            return acc
          }, [])
          if (points.length > 0) {
            try {
              client.writePoints(points, function (err, response) {
                if (err) {
                  console.error(err.message)
                  console.error(response)
                }
              })
            } catch (ex) {
              console.error(ex.message)
            }
          }
        }
      })
    }
  }

  function toMultilineString (influxResult) {
    let currentLine = []
    const result = {
      type: 'MultiLineString',
      coordinates: []
    }

    influxResult.forEach(row => {
      if (row.position === null) {
        currentLine = []
      } else {
        currentLine[currentLine.length] = JSON.parse(row.position)
        if (currentLine.length === 1) {
          result.coordinates[result.coordinates.length] = currentLine
        }
      }
    })
    return result
  }
  function timewindowStart () {
    return new Date(new Date().getTime() - 60 * 60 * 1000).toISOString()
  }

  return {
    id: 'signalk-to-influxdb',
    name: 'InfluxDb writer',
    description: 'Signal K server plugin that writes self values to InfluxDb',

    schema: {
      type: 'object',
      required: ['host', 'port', 'database'],
      properties: {
        host: {
          type: 'string',
          title: 'Host',
          default: 'localhost'
        },
        port: {
          type: 'number',
          title: 'Port',
          default: 8086
        },
        database: {
          type: 'string',
          title: 'Database'
        },
        resolution: {
          type: 'number',
          title: 'Resolution (ms)',
          default: 200
        },
        blackOrWhite: {
          type: 'string',
          title: 'Type of List',
          description:
            'With a blacklist, all numeric values except the ones in the list below will be stored in InfluxDB. With a whitelist, only the values in the list below will be stored.',
          default: 'Black',
          enum: ['White', 'Black']
        },
        blackOrWhitelist: {
          title: 'SignalK Paths',
          description:
            'A list of SignalK paths to be exluded or included based on selection above',
          type: 'array',
          items: {
            type: 'string',
            title: 'Path'
          }
        }
      }
    },

    start: function (options) {
      client = new Influx.InfluxDB({
        host: options.host,
        port: options.port, // optional, default 8086
        protocol: 'http', // optional, default 'http'
        database: options.database
      })

      client
        .getDatabaseNames()
        .then(names => {
          if (!names.includes(options.database)) {
            client.createDatabase(options.database).then(result => {
              console.log('Created InfluxDb database ' + options.database)
            })
          }
        })
        .catch(err => {
          console.error(err)
        })

      if (
        typeof options.blackOrWhitelist !== 'undefined' &&
        typeof options.blackOrWhite !== 'undefined' &&
        options.blackOrWhitelist.length > 0
      ) {
        var obj = {}

        options.blackOrWhitelist.forEach(element => {
          obj[element] = true
        })

        if (options.blackOrWhite == 'White') {
          shouldStore = function (path) {
            return typeof obj[path] !== 'undefined'
          }
        } else {
          shouldStore = function (path) {
            return typeof obj[path] === 'undefined'
          }
        }
      }

      app.signalk.on('delta', handleDelta)

      unsubscribes.push(
        Bacon.combineWith(function (awaDeg, aws, sog, cogDeg) {
          const cog = cogDeg / 180 * Math.PI
          const awa = awaDeg / 180 * Math.PI
          return [
            {
              measurement: 'environmentWindDirectionTrue',
              fields: {
                value: getTrueWindAngle(sog, aws, awa) + cog
              }
            },
            {
              measurement: 'environmentWindSpeedTrue',
              fields: {
                value: getTrueWindSpeed(sog, aws, awa)
              }
            }
          ]
        }, [
          'environment.wind.angleApparent',
          'environment.wind.speedApparent',
          'navigation.speedOverGround',
          'navigation.courseOverGroundTrue'
        ].map(app.streambundle.getSelfStream, app.streambundle))
          .changes()
          .debounceImmediate(options.resolution || 200)
          .onValue(points => {
            try {
              client.writePoints(points, function (err, response) {
                if (err) {
                  console.error(err)
                  console.error(response)
                }
              })
            } catch (ex) {
              console.error(ex.message)
            }
          })
      )
    },
    stop: function () {
      unsubscribes.forEach(f => f())
      app.signalk.removeListener('delta', handleDelta)
    },
    signalKApiRoutes: function (router) {
      const trackHandler = function (req, res, next) {
        if (typeof client === 'undefined') {
          console.error(
            'signalk-to-influxdb plugin not enabled, http track interface not available'
          )
          next()
          return
        }

        let query = `
        select first(value) as "position"
        from "navigation.position"
        where time >= now() - ${sanitize(req.query.timespan || '1h')}
        group by time(${sanitize(req.query.resolution || '1m')})`
        client
          .query(query)
          .then(result => {
            res.type('application/vnd.geo+json')
            res.json(toMultilineString(result))
          })
          .catch(err => {
            console.error(err.message + ' ' + query)
            res.status(500).send(err.message + ' ' + query)
          })
      }

      router.get('/self/track', trackHandler)
      router.get('/vessels/self/track', trackHandler)
      router.get('/vessels/' + app.selfId + '/track', trackHandler)
      return router
    }
  }
}

const influxDurationKeys = {
  s: 's',
  m: 'm',
  h: 'h',
  d: 'd',
  w: 'w'
}

function sanitize (influxTime) {
  return (
    Number(influxTime.substring(0, influxTime.length - 1)) +
    influxDurationKeys[
      influxTime.substring(influxTime.length - 1, influxTime.length)
    ]
  )
}

function getTrueWindAngle (speed, windSpeed, windAngle) {
  var apparentX = Math.cos(windAngle) * windSpeed
  var apparentY = Math.sin(windAngle) * windSpeed
  return Math.atan2(apparentY, -speed + apparentX)
}

function getTrueWindSpeed (speed, windSpeed, windAngle) {
  var apparentX = Math.cos(windAngle) * windSpeed
  var apparentY = Math.sin(windAngle) * windSpeed
  return Math.sqrt(Math.pow(apparentY, 2) + Math.pow(-speed + apparentX, 2))
}
<commit_msg>feature: add boolean switch to enable track recording (#15)<commit_after>/*
 * Copyright 2016 Teppo Kurki <teppo.kurki@iki.fi>
 *
 * Licensed 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.
 */

const Influx = require('influx')
const Bacon = require('baconjs')
const debug = require('debug')('signalk-to-influxdb')
const util = require('util')

module.exports = function (app) {
  let client
  let selfContext = 'vessels.' + app.selfId
  let lastPositionStored = 0
  let recordTrack = false

  let unsubscribes = []
  let shouldStore = function (path) {
    return true
  }

  function handleDelta (delta) {
    if (delta.context === 'vessels.self') {
      delta.context = selfContext
    }

    if (delta.updates && delta.context === selfContext) {
      delta.updates.forEach(update => {
        if (update.values) {
          var points = update.values.reduce((acc, pathValue) => {
            if ( pathValue.path === 'navigation.position' ) {
              if ( recordTrack &&
                   new Date().getTime() - lastPositionStored > 1000
                 ) {
                acc.push({
                  measurement: pathValue.path,
                  fields: {
                    value: JSON.stringify([
                      pathValue.value.longitude,
                      pathValue.value.latitude
                    ])
                  }
                })
                lastPositionStored = new Date().getTime()
              }
            } else if (shouldStore(pathValue.path)) {
              if (typeof pathValue.value === 'number') {
                acc.push({
                  measurement: pathValue.path,
                  fields: {
                    value: pathValue.value
                  }
                })
              }
            }
            return acc
          }, [])
          if (points.length > 0) {
            try {
              client.writePoints(points, function (err, response) {
                if (err) {
                  console.error(err.message)
                  console.error(response)
                }
              })
            } catch (ex) {
              console.error(ex.message)
            }
          }
        }
      })
    }
  }

  function toMultilineString (influxResult) {
    let currentLine = []
    const result = {
      type: 'MultiLineString',
      coordinates: []
    }

    influxResult.forEach(row => {
      if (row.position === null) {
        currentLine = []
      } else {
        currentLine[currentLine.length] = JSON.parse(row.position)
        if (currentLine.length === 1) {
          result.coordinates[result.coordinates.length] = currentLine
        }
      }
    })
    return result
  }
  function timewindowStart () {
    return new Date(new Date().getTime() - 60 * 60 * 1000).toISOString()
  }

  return {
    id: 'signalk-to-influxdb',
    name: 'InfluxDb writer',
    description: 'Signal K server plugin that writes self values to InfluxDb',

    schema: {
      type: 'object',
      required: ['host', 'port', 'database'],
      properties: {
        host: {
          type: 'string',
          title: 'Host',
          default: 'localhost'
        },
        port: {
          type: 'number',
          title: 'Port',
          default: 8086
        },
        database: {
          type: 'string',
          title: 'Database'
        },
        resolution: {
          type: 'number',
          title: 'Resolution (ms)',
          default: 200
        },
        recordTrack: {
          type: "boolean",
          title: "Record Track",
          description: "When enabled the vessels position will be stored",
          default: false
        },
        blackOrWhite: {
          type: 'string',
          title: 'Type of List',
          description:
            'With a blacklist, all numeric values except the ones in the list below will be stored in InfluxDB. With a whitelist, only the values in the list below will be stored.',
          default: 'Black',
          enum: ['White', 'Black']
        },
        blackOrWhitelist: {
          title: 'SignalK Paths',
          description:
            'A list of SignalK paths to be exluded or included based on selection above',
          type: 'array',
          items: {
            type: 'string',
            title: 'Path'
          }
        }
      }
    },

    start: function (options) {
      client = new Influx.InfluxDB({
        host: options.host,
        port: options.port, // optional, default 8086
        protocol: 'http', // optional, default 'http'
        database: options.database
      })

      client
        .getDatabaseNames()
        .then(names => {
          if (!names.includes(options.database)) {
            client.createDatabase(options.database).then(result => {
              console.log('Created InfluxDb database ' + options.database)
            })
          }
        })
        .catch(err => {
          console.error(err)
        })

      if (
        typeof options.blackOrWhitelist !== 'undefined' &&
        typeof options.blackOrWhite !== 'undefined' &&
        options.blackOrWhitelist.length > 0
      ) {
        var obj = {}

        options.blackOrWhitelist.forEach(element => {
          obj[element] = true
        })

        if (options.blackOrWhite == 'White') {
          shouldStore = function (path) {
            return typeof obj[path] !== 'undefined'
          }
        } else {
          shouldStore = function (path) {
            return typeof obj[path] === 'undefined'
          }
        }
      }

      recordTrack = options.recordTrack

      app.signalk.on('delta', handleDelta)

      unsubscribes.push(
        Bacon.combineWith(function (awaDeg, aws, sog, cogDeg) {
          const cog = cogDeg / 180 * Math.PI
          const awa = awaDeg / 180 * Math.PI
          return [
            {
              measurement: 'environmentWindDirectionTrue',
              fields: {
                value: getTrueWindAngle(sog, aws, awa) + cog
              }
            },
            {
              measurement: 'environmentWindSpeedTrue',
              fields: {
                value: getTrueWindSpeed(sog, aws, awa)
              }
            }
          ]
        }, [
          'environment.wind.angleApparent',
          'environment.wind.speedApparent',
          'navigation.speedOverGround',
          'navigation.courseOverGroundTrue'
        ].map(app.streambundle.getSelfStream, app.streambundle))
          .changes()
          .debounceImmediate(options.resolution || 200)
          .onValue(points => {
            try {
              client.writePoints(points, function (err, response) {
                if (err) {
                  console.error(err)
                  console.error(response)
                }
              })
            } catch (ex) {
              console.error(ex.message)
            }
          })
      )
    },
    stop: function () {
      unsubscribes.forEach(f => f())
      app.signalk.removeListener('delta', handleDelta)
    },
    signalKApiRoutes: function (router) {
      const trackHandler = function (req, res, next) {
        if (typeof client === 'undefined') {
          console.error(
            'signalk-to-influxdb plugin not enabled, http track interface not available'
          )
          next()
          return
        }

        let query = `
        select first(value) as "position"
        from "navigation.position"
        where time >= now() - ${sanitize(req.query.timespan || '1h')}
        group by time(${sanitize(req.query.resolution || '1m')})`
        client
          .query(query)
          .then(result => {
            res.type('application/vnd.geo+json')
            res.json(toMultilineString(result))
          })
          .catch(err => {
            console.error(err.message + ' ' + query)
            res.status(500).send(err.message + ' ' + query)
          })
      }

      router.get('/self/track', trackHandler)
      router.get('/vessels/self/track', trackHandler)
      router.get('/vessels/' + app.selfId + '/track', trackHandler)
      return router
    }
  }
}

const influxDurationKeys = {
  s: 's',
  m: 'm',
  h: 'h',
  d: 'd',
  w: 'w'
}

function sanitize (influxTime) {
  return (
    Number(influxTime.substring(0, influxTime.length - 1)) +
    influxDurationKeys[
      influxTime.substring(influxTime.length - 1, influxTime.length)
    ]
  )
}

function getTrueWindAngle (speed, windSpeed, windAngle) {
  var apparentX = Math.cos(windAngle) * windSpeed
  var apparentY = Math.sin(windAngle) * windSpeed
  return Math.atan2(apparentY, -speed + apparentX)
}

function getTrueWindSpeed (speed, windSpeed, windAngle) {
  var apparentX = Math.cos(windAngle) * windSpeed
  var apparentY = Math.sin(windAngle) * windSpeed
  return Math.sqrt(Math.pow(apparentY, 2) + Math.pow(-speed + apparentX, 2))
}
<|endoftext|>


<commit_before>'use strict';
// Modules required
var child_process = require('child_process');
var pty = require('pty');
var async = require('async');
var TelegramBot = require('node-telegram-bot-api');

var config = require('./config');
var token = require('./token').token;

// Init stuff
var shellSettings = config.shellSettings;

var availableLanguages = '';
for(var i in shellSettings){
  if(shellSettings.hasOwnProperty(i)){
    availableLanguages += '- '+i+'\n';
  }
}

shellSettings.python = shellSettings.python2;
shellSettings.js = shellSettings.javascript;

var envs = {};

// Create bot
var bot = new TelegramBot(token, {polling: true});

function killAndRemove(container, callback) {
  container = container || '$(docker ps -a -q)';
  async.series([
    function (next){
      child_process.exec('docker kill '+container, function(){
        next();
      });

    },
    function (next){
      child_process.exec('docker rm '+container, function(){
        next();
      });
    }
  ], function(){
    if(callback){
      callback();
    }
  });
}

function respond(id, data){
  var env = envs[id];
  data = data.split('\r').join('');
  var lines = data.split('\n');
  var lmIdx = lines.indexOf(env.lastMessage);
  if(lmIdx !== -1){
    lines.splice(lmIdx, 1);
  }
  while(lines.indexOf('') !== -1){
    lines.splice(lines.indexOf(''), 1);
  }
  if(!lines.length){
    return;
  }
  bot.sendMessage(id, lines.join('\n'));
}

function createEnv(id, type) {
  id = id.toString();
  var command = shellSettings[type].command.slice();
  command[2] = 'env'+id;
  var env = pty.spawn('docker', command);
  env.stdout.on('data', function (data) {
    respond(id, data.toString());
  });

  env.on('close', function (/*code*/) {
    console.log((new Date().toISOString())+' - session stopped. '+id);
    bot.sendMessage(id, 'Your session stopped.');
    stop(env);
  });

  envs[id] = {
    id: id,
    type: type,
    env: env,
    started: new Date(),
    lastActive: new Date(),
    pid: env.pid,
    lastMessage:shellSettings[type].skipIntro || ''
  };
}

function start(msg) {
  var type = config.defaultLanguage;

  if(msg.text.split(' ').length > 1){
    type = msg.text.split(' ')[1].toLowerCase();
    if(!shellSettings.hasOwnProperty(type)){
      return bot.sendMessage(msg.from.id, 'The language '+type+' is not supported. Type /languages for a list of supported languages.');
    }
  }

  console.log((new Date().toISOString())+' - Spawning '+type+' - '+msg.from.id);

  async.series([
    function (next){
      killAndRemove('env'+msg.from.id.toString(), next);
    },
    function (next) {
      createEnv(msg.from.id, type);
      next();
    }
  ]);

}

function stop(env) {
  killAndRemove('env'+env.id);
  delete envs[env.id];
}

function send(command, env){
  env.lastActive = new Date();
  if(shellSettings[env.type].hasOwnProperty('suffix')){
    command+=shellSettings[env.type].suffix;
  }
  env.lastMessage = command.toString();
  env.env.stdin.write(command+'\n');
}

function languages(id){
  var languages = 'Available languages:\n'+availableLanguages+'Use /start [language] to start a new session.';
  bot.sendMessage(id, languages);
}

function version(id) {
  bot.sendMessage(id,'FiddleGram v1.0 with:\n\
  - Erlang: 17.3\n\
  - JavaScript: Spidermonkey C24.2.0\n\
  - PHP: 5.6.20\n\
  - Python2: 2.7.9\n\
  - Python3: 3.4.2\n\
  - Ruby: 2.1.5\n\
  Use /start [language] to start a new session.');
}

function help(id){
  bot.sendMessage(id,'FiddleGram launches an interactive language shell (REPL) in the language of your choosing, just like at home.\nUse the following commands to use the bot:\n\
    /start - [language] start a new repl session. Default python2\n\
    /stop - stop current repl session\n\
    /languages - list currently supported languages\n\
    /version - list bot and languages versions\n\
    /help - display this help\n\n\
    Make your own FiddleGram bot at https://github.com/LeonardA-L/fiddlegram');
}

// Any kind of message
bot.on('message', function (msg) {
  var env = envs[msg.from.id.toString()];
  //console.log(msg.text);
  if(msg.text.match('/start\s?.*')) {
    if(env){
      return bot.sendMessage(msg.from.id, 'You already have a'+env.type+' session running. Run /stop to stop it.');
    }
    start(msg);
  }
  else if(msg.text === '/stop') {
    stop(env);
  }
  else if(msg.text.indexOf('/lang') === 0){
    languages(msg.from.id);
  }
  else if(msg.text.indexOf('/v') === 0){
    version(msg.from.id);
  }
  else if(msg.text.indexOf('/h') === 0){
    help(msg.from.id);
  }
  else if(env) {
    send(msg.text, env);
  }
  else {
    bot.sendMessage(msg.from.id, 'You don\'t have any env running. Run /start to start one, or /help to get help.');
  }
});

setInterval(function(){
  //console.log((new Date().toISOString())+' - Killing inactive envs');
  var now = new Date();
  for(var i in envs){
    if(envs.hasOwnProperty(i)){
      var e = envs[i];
      var dateDiff = (now - e.lastActive) / 1000;
      if(dateDiff > config.inactiveThreshold){
        console.log((new Date().toISOString())+' - Killing inactive env '+i);
        bot.sendMessage(i, 'Your console session has been inactive for '+config.inactiveThreshold/60+' minutes and has been killed');
        stop(e);
      }
    }
  }
}, config.killInactiveDelay);

async.series([
  function (next){
    killAndRemove(null, next);
  },
  function (next){
    console.log((new Date().toISOString())+' - Bot is up and running');
    next();
  }]
);<commit_msg>Drop non text messages<commit_after>'use strict';
// Modules required
var child_process = require('child_process');
var pty = require('pty');
var async = require('async');
var TelegramBot = require('node-telegram-bot-api');

var config = require('./config');
var token = require('./token').token;

// Init stuff
var shellSettings = config.shellSettings;

var availableLanguages = '';
for(var i in shellSettings){
  if(shellSettings.hasOwnProperty(i)){
    availableLanguages += '- '+i+'\n';
  }
}

shellSettings.python = shellSettings.python2;
shellSettings.js = shellSettings.javascript;

var envs = {};

// Create bot
var bot = new TelegramBot(token, {polling: true});

function killAndRemove(container, callback) {
  container = container || '$(docker ps -a -q)';
  async.series([
    function (next){
      child_process.exec('docker kill '+container, function(){
        next();
      });

    },
    function (next){
      child_process.exec('docker rm '+container, function(){
        next();
      });
    }
  ], function(){
    if(callback){
      callback();
    }
  });
}

function respond(id, data){
  var env = envs[id];
  data = data.split('\r').join('');
  var lines = data.split('\n');
  var lmIdx = lines.indexOf(env.lastMessage);
  if(lmIdx !== -1){
    lines.splice(lmIdx, 1);
  }
  while(lines.indexOf('') !== -1){
    lines.splice(lines.indexOf(''), 1);
  }
  if(!lines.length){
    return;
  }
  bot.sendMessage(id, lines.join('\n'));
}

function createEnv(id, type) {
  id = id.toString();
  var command = shellSettings[type].command.slice();
  command[2] = 'env'+id;
  var env = pty.spawn('docker', command);
  env.stdout.on('data', function (data) {
    respond(id, data.toString());
  });

  env.on('close', function (/*code*/) {
    console.log((new Date().toISOString())+' - session stopped. '+id);
    bot.sendMessage(id, 'Your session stopped.');
    stop(env);
  });

  envs[id] = {
    id: id,
    type: type,
    env: env,
    started: new Date(),
    lastActive: new Date(),
    pid: env.pid,
    lastMessage:shellSettings[type].skipIntro || ''
  };
}

function start(msg) {
  var type = config.defaultLanguage;

  if(msg.text.split(' ').length > 1){
    type = msg.text.split(' ')[1].toLowerCase();
    if(!shellSettings.hasOwnProperty(type)){
      return bot.sendMessage(msg.from.id, 'The language '+type+' is not supported. Type /languages for a list of supported languages.');
    }
  }

  console.log((new Date().toISOString())+' - Spawning '+type+' - '+msg.from.id);

  async.series([
    function (next){
      killAndRemove('env'+msg.from.id.toString(), next);
    },
    function (next) {
      createEnv(msg.from.id, type);
      next();
    }
  ]);

}

function stop(env) {
  killAndRemove('env'+env.id);
  delete envs[env.id];
}

function send(command, env){
  env.lastActive = new Date();
  if(shellSettings[env.type].hasOwnProperty('suffix')){
    command+=shellSettings[env.type].suffix;
  }
  env.lastMessage = command.toString();
  env.env.stdin.write(command+'\n');
}

function languages(id){
  var languages = 'Available languages:\n'+availableLanguages+'Use /start [language] to start a new session.';
  bot.sendMessage(id, languages);
}

function version(id) {
  bot.sendMessage(id,'FiddleGram v1.0 with:\n\
  - Erlang: 17.3\n\
  - JavaScript: Spidermonkey C24.2.0\n\
  - PHP: 5.6.20\n\
  - Python2: 2.7.9\n\
  - Python3: 3.4.2\n\
  - Ruby: 2.1.5\n\
  Use /start [language] to start a new session.');
}

function help(id){
  bot.sendMessage(id,'FiddleGram launches an interactive language shell (REPL) in the language of your choosing, just like at home.\nUse the following commands to use the bot:\n\
    /start - [language] start a new repl session. Default python2\n\
    /stop - stop current repl session\n\
    /languages - list currently supported languages\n\
    /version - list bot and languages versions\n\
    /help - display this help\n\n\
    Make your own FiddleGram bot at https://github.com/LeonardA-L/fiddlegram');
}

// Any kind of message
bot.on('message', function (msg) {
  var env = envs[msg.from.id.toString()];
  //console.log(msg.text);
  if(!msg.text){
    return;
  }
  if(msg.text.match('/start\s?.*')) {
    if(env){
      return bot.sendMessage(msg.from.id, 'You already have a'+env.type+' session running. Run /stop to stop it.');
    }
    start(msg);
  }
  else if(msg.text === '/stop') {
    stop(env);
  }
  else if(msg.text.indexOf('/lang') === 0){
    languages(msg.from.id);
  }
  else if(msg.text.indexOf('/v') === 0){
    version(msg.from.id);
  }
  else if(msg.text.indexOf('/h') === 0){
    help(msg.from.id);
  }
  else if(env) {
    send(msg.text, env);
  }
  else {
    bot.sendMessage(msg.from.id, 'You don\'t have any env running. Run /start to start one, or /help to get help.');
  }
});

setInterval(function(){
  //console.log((new Date().toISOString())+' - Killing inactive envs');
  var now = new Date();
  for(var i in envs){
    if(envs.hasOwnProperty(i)){
      var e = envs[i];
      var dateDiff = (now - e.lastActive) / 1000;
      if(dateDiff > config.inactiveThreshold){
        console.log((new Date().toISOString())+' - Killing inactive env '+i);
        bot.sendMessage(i, 'Your console session has been inactive for '+config.inactiveThreshold/60+' minutes and has been killed');
        stop(e);
      }
    }
  }
}, config.killInactiveDelay);

async.series([
  function (next){
    killAndRemove(null, next);
  },
  function (next){
    console.log((new Date().toISOString())+' - Bot is up and running');
    next();
  }]
);<|endoftext|>


<commit_before>module("lively.ide.tools.JavaScriptWorkspace").requires("lively.ide.CodeEditor", "lively.persistence.BuildSpec", "lively.lang.VM").toRun(function() {

lively.BuildSpec("lively.ide.tools.JavaScriptWorkspace", {
    _BorderColor: null,
    _BorderWidth: 1,
    _Extent: lively.pt(735.0,358.0),
    className: "lively.morphic.Window",
    contentOffset: lively.pt(3.0,22.0),
    draggingEnabled: true,
    droppingEnabled: false,
    highlighted: false,
    layout: { adjustForNewBounds: true },
    name: "JavaScript workspace",
    submorphs: [{
        _Extent: lively.pt(220.0,333.0),
        _Fill: Color.rgb(255,255,255),
        _Position: lively.pt(512.0,22.0),
        className: "lively.morphic.Box",
        droppingEnabled: true,
        layout: {
            adjustForNewBounds: true,
            moveHorizontal: true,
            resizeHeight: true,
            resizeWidth: false
        },
        name: "listContainer",
        sourceModule: "lively.morphic.Core",
        submorphs: [{
            _ClipMode: "auto",
            _Extent: lively.pt(211.0,298.0),
            _Fill: Color.rgb(255,255,255),
            _Position: lively.pt(4.0,29.0),
            _StyleSheet: ".list-item {\n\
        	font-size: 9pt;\n\
        	font-family: sans-serif;\n\
        	white-space: nowrap;\n\
        	border: #AAA 1px solid !important;\n\
        	background-color: transparent !important;\n\
        }",
            allowDeselectClick: false,
            className: "lively.morphic.MorphList",
            droppingEnabled: true,
            isMultipleSelectionList: true,
            itemList: [],
            layout: {
                adjustForNewBounds: true,
                borderSize: 2,
                extentWithoutPlaceholder: lively.pt(305.0,244.0),
                resizeHeight: true,
                resizeWidth: true,
                spacing: 6,
                type: "lively.morphic.Layout.VerticalLayout"
            },
            name: "workspaceVarObserver"
        },{
            _BorderColor: Color.rgb(189,190,192),
            _BorderRadius: 5,
            _BorderWidth: 1,
            _Extent: lively.pt(60.0,20.0),
            _Position: lively.pt(94.0,4.0),
            _StyleClassNames: ["Morph","Button"],
            className: "lively.morphic.Button",
            droppingEnabled: false,
            grabbingEnabled: false,
            label: "depth: 1",
            layout: { moveHorizontal: true },
            name: "depthButton",
            connectionRebuilder: function connectionRebuilder() {
            lively.bindings.connect(this, "fire", this, "doAction", {});
        },
            doAction: function doAction() {
                    var obs = this.get('workspaceVarObserver');
                    var workspace = this.get('workspace');
                    var self = this;
                    $world.prompt("print depth of objects", function(input) {
                        var n = parseInt(input);
                        if (isNaN(n) || n < 0) {
                            Global.show('not a valid input:\n' + input);
                            return;
                        }
                        self.setLabel('depth: ' + n);
                        workspace.state.depth = n;
                        workspace.rerender();
                    }, workspace.state.depth);
                }
        },{
            _BorderColor: Color.rgb(189,190,192),
            _BorderRadius: 5,
            _BorderWidth: 1,
            _Extent: lively.pt(60.0,20.0),
            _Position: lively.pt(156.0,4.0),
            className: "lively.morphic.Button",
            droppingEnabled: false,
            grabbingEnabled: false,
            label: "reset",
            layout: { moveHorizontal: true },
            name: "resetButton",
            sourceModule: "lively.morphic.Widgets",
            value: false,
            connectionRebuilder: function connectionRebuilder() {
            lively.bindings.connect(this, "fire", this, "doAction", {});
        },
            doAction: function doAction() {
                    this.get('workspace').resetState();
                }
        }]
    },{
        _AutocompletionEnabled: true,
        _BorderColor: Color.rgb(95,94,95),
        _Extent: lively.pt(729.0,333.0),
        _FontSize: 12,
        _LineWrapping: false,
        _Position: lively.pt(3.0,22.0),
        _ShowActiveLine: false,
        _ShowErrors: true,
        _ShowGutter: false,
        _ShowIndents: true,
        _ShowInvisibles: false,
        _ShowPrintMargin: false,
        _ShowWarnings: true,
        _SoftTabs: true,
        _TextMode: "javascript",
        _aceInitialized: true,
        allowInput: true,
        className: "lively.morphic.CodeEditor",
        layout: { resizeHeight: true, resizeWidth: true },
        name: "workspace",
        state: {depth: 1,doNotSerialize: ["workspaceVars"],workspaceVars: {}},
        storedString: "// Enter JavaScript code in this text\n\
    // Use Command-d to evaluate and Command-p to print\n\
    // the selected text or current line\n\
    ",
        textMode: "javascript",
        onWindowGetsFocus: function onWindowGetsFocus() { this.focus(); },
        animatedSetBounds: function animatedSetBounds(morph, bounds) {
        // this.setBounds(newEditorBounds);
        // this.animatedSetBounds(this, this.bounds().withExtent(this.getExtent().addXY(10,0)));
        var morphBounds = morph.bounds();
        var posDiff = bounds.topLeft().subPt(morphBounds.topLeft());
        var extentDiff = bounds.extent().subPt(morphBounds.extent());

        var time = 400; // ms
        var steps = 10;
        var stepMove = posDiff.scaleBy(1/steps);
        var stepResize = extentDiff.scaleBy(1/steps);
        animatedScale(steps);

        function animatedScale(step) {
            if (step === 0) { morph.setBounds(bounds); return; }
            morph.moveBy(stepMove);
            morph.resizeBy(stepResize);
            animatedScale.curry(step-1).delay(time/steps / 1000);
        }

    },
        boundEval: function boundEval(__evalStatement) {
            // Evaluate the string argument in a context in which "this" is
            // determined by the reuslt of #getDoitContext
            var ctx = this.getDoitContext() || this,
                result;

            if (!this.state.workspaceVars) this.state.workspaceVars = {};

            lively.lang.VM.runEval(__evalStatement, {
                context: ctx,
                topLevelVarRecorder: this.state.workspaceVars
            }, function(err, _result) { result = err || _result; })
            return result;
        },
        hideVariableArea: function hideVariableArea() {
        var newEditorBounds = rect(pt(3,22), this.get("listContainer").bounds().bottomRight());
        // this.setBounds(newEditorBounds);
        this.updateToggleVarsButton(newEditorBounds);
        this.animatedSetBounds(this, newEditorBounds);
    },

        onLoad: function onLoad() {
            $super();
            // FIXME...
            this.getWindow().addMorphBack(this.get("listContainer"));
        },

        onFromBuildSpecCreated: function onFromBuildSpecCreated() {
            $super();
            this.getWindow().addMorphBack(this.get("listContainer"));
            this.resetState();
            this.startStepping(1000, 'showVars');
        },
        rerender: function rerender() {
            var obs = this.get('workspaceVarObserver');
            obs.setList([]);
            this.showVars();
        },
        reset: function reset() {
        this.setStyleSheet('.list-item { font-size: 9pt; }')
        this.state = {
            doNotSerialize: ['workspaceVars'],
            workspaceVars: {},
            depth: 1
        };
        this.showVars();
        this.textString = '';
        lively.bindings.connect(this.get("toggleVarsButton"), 'fire', this, 'toggleVarArea');
    },
        resetState: function resetState() {
                this.owner.targetMorph = this;
                this.state = {
                depth: 1,
                doNotSerialize: ["workspaceVars"],
                workspaceVars: {}
            }
            this.showVars();
        },
        showVariableArea: function showVariableArea() {
        var border = 2,
            varAreaWidth = 220,
            varList = this.get("listContainer"),
            newEditorBounds = rect(
                pt(3,22),
                varList.bounds().bottomRight().addXY(-3 - varAreaWidth - border, 0));

        varList.setExtent(varList.getExtent().withX(varAreaWidth));
        varList.align(varList.bounds().topRight(), this.getWindow().innerBounds().topRight().addXY(-3,22));

        this.updateToggleVarsButton(newEditorBounds);

        this.animatedSetBounds(this, newEditorBounds);

    },
        showVars: function showVars() {
            // this.startStepping(1000, 'showVars')
            // obs.setList([]);

            var style = {
                allowInput: false,
                fixedHeight: false, fixedWidth: true,
                whiteSpaceHandling: 'pre',
                cssStylingMode: true
            };

            var s = this.state;
            var vars = s.workspaceVars || {};
            var keys = Object.keys(vars);

            var obs = this.get('workspaceVarObserver');
            var list = obs.getList() || [];

            var preexistingItems = list.filter(function(item) {
                    return keys.include(item.value.key); })
                .map(function(ea) {
                    ea.value = {key: ea.value.key, value: vars[ea.value.key]};
                    ea.morph = render(ea.value.key, ea.value.value, ea.morph);
                    return ea
                });

            var newItems = keys
                .withoutAll(preexistingItems.pluck('value').pluck('key'))
                .map(function(k) {
                    var morph = render(k, vars[k]),
                        item = {morph: morph, isListItem: true, string: 'foo', value: {key: k, value: vars[k]}};
                    morph.item = item;
                    return item; });

            var items = preexistingItems.concat(newItems);

            obs.setList(items);
            obs.applyLayout();

            // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-

            function render(key, val, optListItemMorph) {
                var morph = optListItemMorph || createListItemMorph();

                var valString = stringifyObj(val);
                var string = key + ': ' + valString;
                if (morph.textString !== string) {
                    morph.setClipMode('visible');
                    morph.textString = string;
                    morph.fitThenDo(function() { morph.setClipMode("hidden"); });
                };

                return morph;
            }

            function createListItemMorph() {
                var morph = new lively.morphic.Text(
                    obs.getExtent()
                        .addXY(-2 * obs.layout.layouter.borderSize, 0)
                        .extentAsRectangle(), '');

                morph.applyStyle(style);

                morph.addStyleClassName('list-item');

                morph.addScript(function showControls() {
                    var resetButton = new lively.morphic.Button(this.innerBounds());
                    resetButton.align(resetButton.getPosition(), this.innerBounds().bottomLeft());
                    this.controls = [
                        this.addMorph(resetButton)
                    ];
                    this.cachedBounds = null;
                    this.owner.applyLayout();
                });

                morph.addScript(function hideControls() {
                    this.controls.invoke('remove');
                    this.controls.length = 0;
                    this.owner.applyLayout();
                })

                morph.addScript(function onHoverIn(evt) {
                    this.showControls();
                });

                morph.addScript(function onHoverOut(evt) {
                    this.hideControls();
                });

                morph.addScript(function onMouseMove(evt) {
                    if (this.thereIsAHandInMe) return false;
                    this.thereIsAHandInMe = true;
                    this.onHoverIn(evt);
                });

                morph.addScript(function onMouseOut(evt) {
                    var wasHovered = !!this.thereIsAHandInMe;
                    var hoverInMorph = evt.relatedTarget && lively.$(evt.relatedTarget).parents('.morphNode').data('morph');
                    if (hoverInMorph && (hoverInMorph === this || hoverInMorph.ownerChain().include(this))) return;
                    this.thereIsAHandInMe = false;
                    wasHovered && this.onHoverOut(evt);
                });

                return morph;
            }

            function stringifyObj(obj) {
                var string;
                if (obj && obj.isMorph) {
                    string = obj.toString();
                } else {
                     string = Objects.inspect(obj, {maxDepth: s.depth});
                }
                return string.truncate(250);
            }
        },
        toggleVarArea: function toggleVarArea() {
        this[this.varAreaIsHidden() ? "showVariableArea" : "hideVariableArea"]();
    },
        updateToggleVarsButton: function updateToggleVarsButton(bnds) {
        var btn = this.get("toggleVarsButton");
        bnds = bnds || this.bounds();

        var newButtonBounds = btn.bounds().withTopRight(bnds.topRight().addXY(-4,4))
        this.animatedSetBounds(btn, newButtonBounds);

        var hidden = this.varAreaIsHidden();
        var label = hidden ? "hide vars" : "show vars";
        btn.setLabel(label);
    },
        varAreaIsHidden: function varAreaIsHidden() {
        this.cachedBounds = null;
        this.aceEditor.resize(true)
        var varsAreVisible = this.getWindow().getExtent().x - this.bounds().width > 30;
        return !varsAreVisible
    }
    },{
        _BorderColor: Color.rgb(189,190,192),
        _BorderRadius: 5,
        _BorderWidth: 1,
        _Extent: lively.pt(64.0,20.0),
        _Position: lively.pt(664.0,26.0),
        layout: {moveHorizontal: true},
        className: "lively.morphic.Button",
        droppingEnabled: false,
        grabbingEnabled: false,
        label: "show vars",
        name: "toggleVarsButton",
        sourceModule: "lively.morphic.Widgets",
        connectionRebuilder: function connectionRebuilder() {
        lively.bindings.connect(this, "fire", this.get("workspace"), "toggleVarArea", {});
    }
    }],
    titleBar: "JavaScript Workspace",
    onKeyDown: function onKeyDown(evt) {
        var keys = evt.getKeyString();
        if (keys === "Control-Shift-V" || keys === "Command-Shift-V") {
            this.targetMorph.toggleVarArea();
            evt.stop(); return true;
        }
        $super(evt);
    }
});

Object.extend(lively.ide.tools.JavaScriptWorkspace, {
    open: function() {
        return lively.BuildSpec('lively.ide.tools.JavaScriptWorkspace')
            .createMorph().openInWorld($world.positionForNewMorph()).comeForward();
    }
});

}) // end of module
<commit_msg>enhanced JS workspace: don't update when var area not visible<commit_after>module("lively.ide.tools.JavaScriptWorkspace").requires("lively.ide.CodeEditor", "lively.persistence.BuildSpec", "lively.lang.VM").toRun(function() {

lively.BuildSpec("lively.ide.tools.JavaScriptWorkspace", {
    _BorderColor: null,
    _BorderWidth: 1,
    _Extent: lively.pt(735.0,358.0),
    className: "lively.morphic.Window",
    contentOffset: lively.pt(3.0,22.0),
    draggingEnabled: true,
    droppingEnabled: false,
    highlighted: false,
    layout: { adjustForNewBounds: true },
    name: "JavaScript workspace",
    submorphs: [{
        _Extent: lively.pt(220.0,333.0),
        _Fill: Color.rgb(255,255,255),
        _Position: lively.pt(512.0,22.0),
        className: "lively.morphic.Box",
        droppingEnabled: true,
        layout: {
            adjustForNewBounds: true,
            moveHorizontal: true,
            resizeHeight: true,
            resizeWidth: false
        },
        name: "listContainer",
        sourceModule: "lively.morphic.Core",
        submorphs: [{
            _ClipMode: "auto",
            _Extent: lively.pt(211.0,298.0),
            _Fill: Color.rgb(255,255,255),
            _Position: lively.pt(4.0,29.0),
            _StyleSheet: ".list-item {\n\
        	font-size: 9pt;\n\
        	font-family: sans-serif;\n\
        	white-space: nowrap;\n\
        	border: #AAA 1px solid !important;\n\
        	background-color: transparent !important;\n\
        }",
            allowDeselectClick: false,
            className: "lively.morphic.MorphList",
            droppingEnabled: true,
            isMultipleSelectionList: true,
            itemList: [],
            layout: {
                adjustForNewBounds: true,
                borderSize: 2,
                extentWithoutPlaceholder: lively.pt(305.0,244.0),
                resizeHeight: true,
                resizeWidth: true,
                spacing: 6,
                type: "lively.morphic.Layout.VerticalLayout"
            },
            name: "workspaceVarObserver"
        },{
            _BorderColor: Color.rgb(189,190,192),
            _BorderRadius: 5,
            _BorderWidth: 1,
            _Extent: lively.pt(60.0,20.0),
            _Position: lively.pt(94.0,4.0),
            _StyleClassNames: ["Morph","Button"],
            className: "lively.morphic.Button",
            droppingEnabled: false,
            grabbingEnabled: false,
            label: "depth: 1",
            layout: { moveHorizontal: true },
            name: "depthButton",
            connectionRebuilder: function connectionRebuilder() {
            lively.bindings.connect(this, "fire", this, "doAction", {});
        },
            doAction: function doAction() {
                    var obs = this.get('workspaceVarObserver');
                    var workspace = this.get('workspace');
                    var self = this;
                    $world.prompt("print depth of objects", function(input) {
                        var n = parseInt(input);
                        if (isNaN(n) || n < 0) {
                            Global.show('not a valid input:\n' + input);
                            return;
                        }
                        self.setLabel('depth: ' + n);
                        workspace.state.depth = n;
                        workspace.rerender();
                    }, workspace.state.depth);
                }
        },{
            _BorderColor: Color.rgb(189,190,192),
            _BorderRadius: 5,
            _BorderWidth: 1,
            _Extent: lively.pt(60.0,20.0),
            _Position: lively.pt(156.0,4.0),
            className: "lively.morphic.Button",
            droppingEnabled: false,
            grabbingEnabled: false,
            label: "reset",
            layout: { moveHorizontal: true },
            name: "resetButton",
            sourceModule: "lively.morphic.Widgets",
            value: false,
            connectionRebuilder: function connectionRebuilder() {
            lively.bindings.connect(this, "fire", this, "doAction", {});
        },
            doAction: function doAction() {
                    this.get('workspace').resetState();
                }
        }]
    },{
        _AutocompletionEnabled: true,
        _BorderColor: Color.rgb(95,94,95),
        _Extent: lively.pt(729.0,333.0),
        _FontSize: 12,
        _LineWrapping: false,
        _Position: lively.pt(3.0,22.0),
        _ShowActiveLine: false,
        _ShowErrors: true,
        _ShowGutter: false,
        _ShowIndents: true,
        _ShowInvisibles: false,
        _ShowPrintMargin: false,
        _ShowWarnings: true,
        _SoftTabs: true,
        _TextMode: "javascript",
        _aceInitialized: true,
        allowInput: true,
        className: "lively.morphic.CodeEditor",
        layout: { resizeHeight: true, resizeWidth: true },
        name: "workspace",
        state: {depth: 1,doNotSerialize: ["workspaceVars"],workspaceVars: {}},
        storedString: "// Enter JavaScript code in this text\n\
    // Use Command-d to evaluate and Command-p to print\n\
    // the selected text or current line\n\
    ",
        textMode: "javascript",
        onWindowGetsFocus: function onWindowGetsFocus() { this.focus(); },
        animatedSetBounds: function animatedSetBounds(morph, bounds) {
        // this.setBounds(newEditorBounds);
        // this.animatedSetBounds(this, this.bounds().withExtent(this.getExtent().addXY(10,0)));
        var morphBounds = morph.bounds();
        var posDiff = bounds.topLeft().subPt(morphBounds.topLeft());
        var extentDiff = bounds.extent().subPt(morphBounds.extent());

        var time = 400; // ms
        var steps = 10;
        var stepMove = posDiff.scaleBy(1/steps);
        var stepResize = extentDiff.scaleBy(1/steps);
        animatedScale(steps);

        function animatedScale(step) {
            if (step === 0) { morph.setBounds(bounds); return; }
            morph.moveBy(stepMove);
            morph.resizeBy(stepResize);
            animatedScale.curry(step-1).delay(time/steps / 1000);
        }

    },
        boundEval: function boundEval(__evalStatement) {
            // Evaluate the string argument in a context in which "this" is
            // determined by the reuslt of #getDoitContext
            var ctx = this.getDoitContext() || this,
                result;

            if (!this.state.workspaceVars) this.state.workspaceVars = {};

            lively.lang.VM.runEval(__evalStatement, {
                context: ctx,
                topLevelVarRecorder: this.state.workspaceVars
            }, function(err, _result) { result = err || _result; })
            return result;
        },
        hideVariableArea: function hideVariableArea() {
        var newEditorBounds = rect(pt(3,22), this.get("listContainer").bounds().bottomRight());
        // this.setBounds(newEditorBounds);
        this.updateToggleVarsButton(newEditorBounds);
        this.animatedSetBounds(this, newEditorBounds);
        this.stopStepping();
    },

        onLoad: function onLoad() {
            $super();
            // FIXME...
            this.getWindow().addMorphBack(this.get("listContainer"));
        },

        onFromBuildSpecCreated: function onFromBuildSpecCreated() {
            $super();
            this.getWindow().addMorphBack(this.get("listContainer"));
            this.resetState();
        },
        rerender: function rerender() {
            var obs = this.get('workspaceVarObserver');
            obs.setList([]);
            this.showVars();
        },
        reset: function reset() {
        this.setStyleSheet('.list-item { font-size: 9pt; }')
        this.state = {
            doNotSerialize: ['workspaceVars'],
            workspaceVars: {},
            depth: 1
        };
        this.showVars();
        this.textString = '';
        lively.bindings.connect(this.get("toggleVarsButton"), 'fire', this, 'toggleVarArea');
    },
        resetState: function resetState() {
                this.owner.targetMorph = this;
                this.state = {
                depth: 1,
                doNotSerialize: ["workspaceVars"],
                workspaceVars: {}
            }
            this.showVars();
        },
        showVariableArea: function showVariableArea() {
        var border = 2,
            varAreaWidth = 220,
            varList = this.get("listContainer"),
            newEditorBounds = rect(
                pt(3,22),
                varList.bounds().bottomRight().addXY(-3 - varAreaWidth - border, 0));

        varList.setExtent(varList.getExtent().withX(varAreaWidth));
        varList.align(varList.bounds().topRight(), this.getWindow().innerBounds().topRight().addXY(-3,22));

        this.updateToggleVarsButton(newEditorBounds);

        this.animatedSetBounds(this, newEditorBounds);
        this.startStepping(1000, "showVars");

    },
        showVars: function showVars() {
            // this.startStepping(1000, 'showVars')
            // obs.setList([]);

            var style = {
                allowInput: false,
                fixedHeight: false, fixedWidth: true,
                whiteSpaceHandling: 'pre',
                cssStylingMode: true
            };

            var s = this.state;
            var vars = s.workspaceVars || {};
            var keys = Object.keys(vars);

            var obs = this.get('workspaceVarObserver');
            var list = obs.getList() || [];

            var preexistingItems = list.filter(function(item) {
                    return keys.include(item.value.key); })
                .map(function(ea) {
                    ea.value = {key: ea.value.key, value: vars[ea.value.key]};
                    ea.morph = render(ea.value.key, ea.value.value, ea.morph);
                    return ea
                });

            var newItems = keys
                .withoutAll(preexistingItems.pluck('value').pluck('key'))
                .map(function(k) {
                    var morph = render(k, vars[k]),
                        item = {morph: morph, isListItem: true, string: 'foo', value: {key: k, value: vars[k]}};
                    morph.item = item;
                    return item; });

            var items = preexistingItems.concat(newItems);

            obs.setList(items);
            obs.applyLayout();

            // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-

            function render(key, val, optListItemMorph) {
                var morph = optListItemMorph || createListItemMorph();

                var valString = stringifyObj(val);
                var string = key + ': ' + valString;
                if (morph.textString !== string) {
                    morph.setClipMode('visible');
                    morph.textString = string;
                    morph.fitThenDo(function() { morph.setClipMode("hidden"); });
                };

                return morph;
            }

            function createListItemMorph() {
                var morph = new lively.morphic.Text(
                    obs.getExtent()
                        .addXY(-2 * obs.layout.layouter.borderSize, 0)
                        .extentAsRectangle(), '');

                morph.applyStyle(style);

                morph.addStyleClassName('list-item');

                morph.addScript(function showControls() {
                    var resetButton = new lively.morphic.Button(this.innerBounds());
                    resetButton.align(resetButton.getPosition(), this.innerBounds().bottomLeft());
                    this.controls = [
                        this.addMorph(resetButton)
                    ];
                    this.cachedBounds = null;
                    this.owner.applyLayout();
                });

                morph.addScript(function hideControls() {
                    this.controls.invoke('remove');
                    this.controls.length = 0;
                    this.owner.applyLayout();
                })

                morph.addScript(function onHoverIn(evt) {
                    this.showControls();
                });

                morph.addScript(function onHoverOut(evt) {
                    this.hideControls();
                });

                morph.addScript(function onMouseMove(evt) {
                    if (this.thereIsAHandInMe) return false;
                    this.thereIsAHandInMe = true;
                    this.onHoverIn(evt);
                });

                morph.addScript(function onMouseOut(evt) {
                    var wasHovered = !!this.thereIsAHandInMe;
                    var hoverInMorph = evt.relatedTarget && lively.$(evt.relatedTarget).parents('.morphNode').data('morph');
                    if (hoverInMorph && (hoverInMorph === this || hoverInMorph.ownerChain().include(this))) return;
                    this.thereIsAHandInMe = false;
                    wasHovered && this.onHoverOut(evt);
                });

                return morph;
            }

            function stringifyObj(obj) {
                var string;
                if (obj && obj.isMorph) {
                    string = obj.toString();
                } else {
                     string = Objects.inspect(obj, {maxDepth: s.depth});
                }
                return string.truncate(250);
            }
        },
        toggleVarArea: function toggleVarArea() {
        this[this.varAreaIsHidden() ? "showVariableArea" : "hideVariableArea"]();
    },
        updateToggleVarsButton: function updateToggleVarsButton(bnds) {
        var btn = this.get("toggleVarsButton");
        bnds = bnds || this.bounds();

        var newButtonBounds = btn.bounds().withTopRight(bnds.topRight().addXY(-4,4))
        this.animatedSetBounds(btn, newButtonBounds);

        var hidden = this.varAreaIsHidden();
        var label = hidden ? "hide vars" : "show vars";
        btn.setLabel(label);
    },
        varAreaIsHidden: function varAreaIsHidden() {
        this.cachedBounds = null;
        this.aceEditor.resize(true)
        var varsAreVisible = this.getWindow().getExtent().x - this.bounds().width > 30;
        return !varsAreVisible
    }
    },{
        _BorderColor: Color.rgb(189,190,192),
        _BorderRadius: 5,
        _BorderWidth: 1,
        _Extent: lively.pt(64.0,20.0),
        _Position: lively.pt(664.0,26.0),
        layout: {moveHorizontal: true},
        className: "lively.morphic.Button",
        droppingEnabled: false,
        grabbingEnabled: false,
        label: "show vars",
        name: "toggleVarsButton",
        sourceModule: "lively.morphic.Widgets",
        connectionRebuilder: function connectionRebuilder() {
        lively.bindings.connect(this, "fire", this.get("workspace"), "toggleVarArea", {});
    }
    }],
    titleBar: "JavaScript Workspace",
    onKeyDown: function onKeyDown(evt) {
        var keys = evt.getKeyString();
        if (keys === "Control-Shift-V" || keys === "Command-Shift-V") {
            this.targetMorph.toggleVarArea();
            evt.stop(); return true;
        }
        $super(evt);
    }
});

Object.extend(lively.ide.tools.JavaScriptWorkspace, {
    open: function() {
        return lively.BuildSpec('lively.ide.tools.JavaScriptWorkspace')
            .createMorph().openInWorld($world.positionForNewMorph()).comeForward();
    }
});

}) // end of module
<|endoftext|>


<commit_before>describe('ng-table', function () {
    var elm, scope;

    beforeEach(module('ngTable'));

    beforeEach(inject(function ($rootScope, $compile, $q) {
        elm = angular.element(
            '
' +
                '' +
                '
' +
                '
' +
                '' +
                '{{user.name}}' +
                '	' +
                '' +
                '{{user.age}}' +
                '	' +
                '' +
                '{{user.money}}' +
                '	' +
                '
' +
                '
' +
                '
');

        scope = $rootScope.$new(true);

        scope.money = function() {
            var def = $q.defer();

            def.resolve([{
                'id': 10,
                'title': '10'
            }]);
            return def;
        };

        $compile(elm)(scope);
        scope.$digest();
    }));

    it('should create table header', inject(function ($compile, $rootScope) {
        var thead = elm.find('table thead');
        expect(thead.length).toBe(1);

        var rows = thead.find('tr');
        expect(rows.length).toBe(2);

        var titles = rows.eq(0).find('th');

        expect(titles.length).toBe(3);
        expect(titles.eq(0).text()).toBe('Name of person');
        expect(titles.eq(1).text()).toBe('Age');
        expect(titles.eq(2).text()).toBe('Money');

        var filters = rows.eq(1).find('th');
        expect(filters.length).toBe(3);
    }));

    it('should show scope data', inject(function ($compile, $rootScope) {
        scope.users = [
            {id: 1, name: "Moroni", age: 50, money: -10},
            {id: 2, name: "Tiancum", age: 43, money: 120},
            {id: 3, name: "Jacob", age: 27, money: 5.5},
            {id: 4, name: "Nephi", age: 29, money: -54},
            {id: 5, name: "Enos", age: 34, money: 110},
            {id: 6, name: "Tiancum", age: 43, money: 1000},
            {id: 7, name: "Jacob", age: 27, money: -201},
            {id: 8, name: "Nephi", age: 29, money: 100},
            {id: 9, name: "Enos", age: 34, money: -52.5},
            {id: 10, name: "Tiancum", age: 43, money: 52.1},
            {id: 11, name: "Jacob", age: 27, money: 110},
            {id: 12, name: "Nephi", age: 29, money: -55},
            {id: 13, name: "Enos", age: 34, money: 551},
            {id: 14, name: "Tiancum", age: 43, money: -1410},
            {id: 15, name: "Jacob", age: 27, money: 410},
            {id: 16, name: "Nephi", age: 29, money: 100},
            {id: 17, name: "Enos", age: 34, money: -100}
        ];

        scope.$digest();

        var tbody = elm.find('table tbody');
        expect(tbody.length).toBe(1);

        var rows = tbody.find('tr');
        expect(rows.length).toBe(scope.users.length);
    }));

});

describe('ngTableParams', function () {
    var scope, ctrl;

    beforeEach(module('ngTable'));

    beforeEach(inject(function ($controller, $rootScope) {
        scope = $rootScope.$new();
    }));

    it('ngTableParams should be defined', inject(function (ngTableParams) {
        var params = new ngTableParams();
        expect(ngTableParams).toBeDefined();
    }));

    it('ngTableParams `page` parameter', inject(function (ngTableParams) {
        var params = new ngTableParams();

        expect(params.page).toBe(1);
        params.page = 2;
        expect(params.page).toBe(2);

        params = new ngTableParams({
            page: 3
        });
        expect(params.page).toBe(3);

        var callCount = 0;
        scope.tableParams = params;
        scope.$watch('tableParams', function (innerParams) {
            callCount++;
            expect(innerParams.page).toBe(4);
        });
        params.page = 4;
        scope.$apply();
        expect(callCount).toBe(1);
        // repeat call
        scope.$apply();
        expect(callCount).toBe(1);
    }));
    it('ngTableParams parse url parameters', inject(function (ngTableParams) {
        var params = new ngTableParams({
            'sorting[name]': 'asc',
            'sorting[age]': 'desc',
            'filter[name]': 'test',
            'filter[age]': 20
        });

        expect(params.filter).toEqual({ 'name': 'test', 'age': 20 });
        expect(params.sorting).toEqual({ 'age': 'desc' }); // sorting only by one column

    }));
    it('ngTableParams return url parameters', inject(function (ngTableParams) {
        var params = new ngTableParams({
            'sorting[name]': 'asc',
            'sorting[age]': 'desc',
            'filter[name]': 'test',
            'filter[age]': 20
        });
        expect(params.url()).toEqual({
            'page': '1',
            'count': '1',
            'filter[name]': 'test',
            'filter[age]': '20',
            'sorting[age]': 'desc'
        });
        expect(params.url(true)).toEqual([
            'page=1',
            'count=1',
            'filter[name]=test',
            'filter[age]=20',
            'sorting[age]=desc'
        ]);
    }));
    it('ngTableParams test orderBy', inject(function (ngTableParams) {
        var params = new ngTableParams({
            'sorting[name]': 'asc'
        });

        expect(params.orderBy()).toEqual([ '+name' ]); // for angular sorting function

        params.sorting.name = 'desc';
        params.sorting.age = 'asc';

        expect(params.orderBy()).toEqual([ '-name', '+age' ]);
    }));

});<commit_msg>add tests for pagination<commit_after>describe('ng-table', function () {
    var elm, scope, data = [
        {id: 1, name: "Moroni", age: 50, money: -10},
        {id: 2, name: "Tiancum", age: 43, money: 120},
        {id: 3, name: "Jacob", age: 27, money: 5.5},
        {id: 4, name: "Nephi", age: 29, money: -54},
        {id: 5, name: "Enos", age: 34, money: 110},
        {id: 6, name: "Tiancum", age: 43, money: 1000},
        {id: 7, name: "Jacob", age: 27, money: -201},
        {id: 8, name: "Nephi", age: 29, money: 100},
        {id: 9, name: "Enos", age: 34, money: -52.5},
        {id: 10, name: "Tiancum", age: 43, money: 52.1},
        {id: 11, name: "Jacob", age: 27, money: 110},
        {id: 12, name: "Nephi", age: 29, money: -55},
        {id: 13, name: "Enos", age: 34, money: 551},
        {id: 14, name: "Tiancum", age: 43, money: -1410},
        {id: 15, name: "Jacob", age: 27, money: 410},
        {id: 16, name: "Nephi", age: 29, money: 100},
        {id: 17, name: "Enos", age: 34, money: -100}
    ];

    beforeEach(module('ngTable'));

    beforeEach(inject(function ($rootScope, $compile, $q) {
        elm = angular.element(
            '
' +
                '' +
                '
' +
                '
' +
                '' +
                '{{user.name}}' +
                '	' +
                '' +
                '{{user.age}}' +
                '	' +
                '' +
                '{{user.money}}' +
                '	' +
                '
' +
                '
' +
                '
');

        scope = $rootScope.$new(true);

        scope.money = function() {
            var def = $q.defer();

            def.resolve([{
                'id': 10,
                'title': '10'
            }]);
            return def;
        };

        $compile(elm)(scope);
        scope.$digest();
    }));

    it('should create table header', inject(function ($compile, $rootScope) {
        var thead = elm.find('table thead');
        expect(thead.length).toBe(1);

        var rows = thead.find('tr');
        expect(rows.length).toBe(2);

        var titles = rows.eq(0).find('th');

        expect(titles.length).toBe(3);
        expect(titles.eq(0).text()).toBe('Name of person');
        expect(titles.eq(1).text()).toBe('Age');
        expect(titles.eq(2).text()).toBe('Money');

        var filters = rows.eq(1).find('th');
        expect(filters.length).toBe(3);
    }));

    it('should show scope data', inject(function ($compile, $rootScope, ngTableParams) {
        scope.users = data;

        scope.$digest();

        var tbody = elm.find('table tbody');
        expect(tbody.length).toBe(1);

        var rows = tbody.find('tr');
        expect(rows.length).toBe(scope.users.length);

        var params = new ngTableParams({
            page: 1,            // show first page
            total: data.length, // length of data
            count: 10           // count per page
        });
        scope.tableParams = params;
        scope.$watch('tableParams', function(params) {
            scope.users = data.slice((params.page - 1) * params.count, params.page * params.count);
        }, true);
        scope.$digest();

        rows = tbody.find('tr');
        expect(rows.length).toBe(10);

        scope.tableParams.page = 2;
        scope.$digest();

        rows = tbody.find('tr');
        expect(rows.length).toBe(7);
    }));

});

describe('ngTableParams', function () {
    var scope, ctrl;

    beforeEach(module('ngTable'));

    beforeEach(inject(function ($controller, $rootScope) {
        scope = $rootScope.$new();
    }));

    it('ngTableParams should be defined', inject(function (ngTableParams) {
        var params = new ngTableParams();
        expect(ngTableParams).toBeDefined();
    }));

    it('ngTableParams `page` parameter', inject(function (ngTableParams) {
        var params = new ngTableParams();

        expect(params.page).toBe(1);
        params.page = 2;
        expect(params.page).toBe(2);

        params = new ngTableParams({
            page: 3
        });
        expect(params.page).toBe(3);

        var callCount = 0;
        scope.tableParams = params;
        scope.$watch('tableParams', function (innerParams) {
            callCount++;
            expect(innerParams.page).toBe(4);
        });
        params.page = 4;
        scope.$apply();
        expect(callCount).toBe(1);
        // repeat call
        scope.$apply();
        expect(callCount).toBe(1);
    }));
    it('ngTableParams parse url parameters', inject(function (ngTableParams) {
        var params = new ngTableParams({
            'sorting[name]': 'asc',
            'sorting[age]': 'desc',
            'filter[name]': 'test',
            'filter[age]': 20
        });

        expect(params.filter).toEqual({ 'name': 'test', 'age': 20 });
        expect(params.sorting).toEqual({ 'age': 'desc' }); // sorting only by one column

    }));
    it('ngTableParams return url parameters', inject(function (ngTableParams) {
        var params = new ngTableParams({
            'sorting[name]': 'asc',
            'sorting[age]': 'desc',
            'filter[name]': 'test',
            'filter[age]': 20
        });
        expect(params.url()).toEqual({
            'page': '1',
            'count': '1',
            'filter[name]': 'test',
            'filter[age]': '20',
            'sorting[age]': 'desc'
        });
        expect(params.url(true)).toEqual([
            'page=1',
            'count=1',
            'filter[name]=test',
            'filter[age]=20',
            'sorting[age]=desc'
        ]);
    }));
    it('ngTableParams test orderBy', inject(function (ngTableParams) {
        var params = new ngTableParams({
            'sorting[name]': 'asc'
        });

        expect(params.orderBy()).toEqual([ '+name' ]); // for angular sorting function

        params.sorting.name = 'desc';
        params.sorting.age = 'asc';

        expect(params.orderBy()).toEqual([ '-name', '+age' ]);
    }));

});<|endoftext|>


<commit_before>var app = require('express')(),
    server = require('http').Server(app),
    io = require('socket.io')(server),
    mumu = new (require('./src/mumu'))(),
    locations = {};

server.listen(process.env.PORT || 5000);

app.get('/:command', function(req, res, next){
    res.json(mumu.execute(req.params.command, req.query.args));
});

io.on('connection', function (socket) {
    // greet connected user
    socket.emit('chat', mumu.execute('greet'));

    // listening chat
    socket.on('chat', function(data){
        // try to find mumu command
        var iscommand = false,
            message = '',
            tmp = [],
            command = '',
            args = '';

        for(var i=0; i<data.message.length; i++){
            message = data.message[i];
            if(message.indexOf(mumu.tag) === 0){
                iscommand = true;
                tmp = message.split(' ');
                command = tmp[1];
                args = tmp.slice(2).join(' ') || '';
                socket.emit('chat', mumu.execute(command, args));
            }
        }

        if(!iscommand){
            socket.broadcast.emit('chat', data);
        }
    });

    // emit locations previously known
    for(var i in locations) if(locations.hasOwnProperty(i)) {
        socket.emit('map', locations[i]);
    }

    // listening location for map
    socket.on('map', function(data){
        socket.broadcast.emit('map', data);
        locations[data.name] = data;
    });
});<commit_msg>First release, v1.0.0<commit_after>var app = require('express')(),
    server = require('http').Server(app),
    io = require('socket.io')(server),
    mumu = new (require('./src/mumu'))(),
    locations = {};

server.listen(process.env.PORT || 5000);

app.get('/:command', function(req, res, next){
    res.json(mumu.execute(req.params.command, req.query.args));
});

io.on('connection', function (socket) {
    // greet connected user
    socket.emit('chat', mumu.execute('greet'));

    // listening chat
    socket.on('chat', function(data){
        console.log('chat data', data);
        // try to find mumu command
        var iscommand = false,
            message = '',
            tmp = [],
            command = '',
            args = '';

        for(var i=0; i<data.message.length; i++){
            message = data.message[i];
            if(message.indexOf(mumu.tag) === 0){
                iscommand = true;
                tmp = message.split(' ');
                command = tmp[1];
                args = tmp.slice(2).join(' ') || '';
                socket.emit('chat', mumu.execute(command, args));
            }
        }

        if(!iscommand){
            socket.broadcast.emit('chat', data);
        }
    });

    // emit locations previously known
    for(var i in locations) if(locations.hasOwnProperty(i)) {
        socket.emit('map', locations[i]);
    }

    // listening location for map
    socket.on('map', function(data){
        socket.broadcast.emit('map', data);
        locations[data.name] = data;
    });
});<|endoftext|>


<commit_before>'use strict';

var BinBuild = require('bin-build');
var BinWrapper = require('bin-wrapper');
var fs = require('fs');
var logSymbols = require('log-symbols');
var path = require('path');
var pkg = require('./package.json');

/**
 * Variables
 */

var BIN_VERSION = '2.3.0';
var BASE_URL = 'https://raw.github.com/kevva/pngquant-bin/v' + pkg.version + '/vendor/';

/**
 * Initialize a new BinWrapper
 */

var bin = new BinWrapper()
	.src(BASE_URL + 'osx/pngquant', 'darwin')
	.src(BASE_URL + 'linux/x86/pngquant', 'linux', 'x86')
	.src(BASE_URL + 'linux/x64/pngquant', 'linux', 'x64')
	.src(BASE_URL + 'win/pngquant.exe', 'win32')
	.dest(path.join(__dirname, 'vendor'))
	.use(process.platform === 'win32' ? 'pngquant.exe' : 'pngquant');

/**
 * Only run check if binary doesn't already exist
 */

fs.exists(bin.use(), function (exists) {
	if (!exists) {
		bin.run(['--version'], function (err) {
			if (err) {
				console.log(logSymbols.warning + ' pre-build test failed, compiling from source...');

				var builder = new BinBuild()
					.src('https://github.com/pornel/pngquant/archive/' + BIN_VERSION + '.tar.gz')
					.make('make install BINPREFIX="' + bin.dest() + '"');

				return builder.build(function (err) {
					if (err) {
						console.log(logSymbols.error + ' pngquant failed to build, make sure that ' + process.platform === 'darwin' ? 'libpng' : 'libpng-dev' + ' is installed');
						console.log('');
						console.log(err);

						return;
					}

					console.log(logSymbols.success + ' pngquant built successfully!');
				});
			}

			console.log(logSymbols.success + ' pre-build test passed successfully!');
		});
	}
});

/**
 * Module exports
 */

module.exports.path = bin.use();
<commit_msg>Fix build error message<commit_after>'use strict';

var BinBuild = require('bin-build');
var BinWrapper = require('bin-wrapper');
var fs = require('fs');
var logSymbols = require('log-symbols');
var path = require('path');
var pkg = require('./package.json');

/**
 * Variables
 */

var BIN_VERSION = '2.3.0';
var BASE_URL = 'https://raw.github.com/kevva/pngquant-bin/v' + pkg.version + '/vendor/';

/**
 * Initialize a new BinWrapper
 */

var bin = new BinWrapper()
	.src(BASE_URL + 'osx/pngquant', 'darwin')
	.src(BASE_URL + 'linux/x86/pngquant', 'linux', 'x86')
	.src(BASE_URL + 'linux/x64/pngquant', 'linux', 'x64')
	.src(BASE_URL + 'win/pngquant.exe', 'win32')
	.dest(path.join(__dirname, 'vendor'))
	.use(process.platform === 'win32' ? 'pngquant.exe' : 'pngquant');

/**
 * Only run check if binary doesn't already exist
 */

fs.exists(bin.use(), function (exists) {
	if (!exists) {
		bin.run(['--version'], function (err) {
			if (err) {
				console.log(logSymbols.warning + ' pre-build test failed, compiling from source...');

				var builder = new BinBuild()
					.src('https://github.com/pornel/pngquant/archive/' + BIN_VERSION + '.tar.gz')
					.make('make install BINPREFIX="' + bin.dest() + '"');

				return builder.build(function (err) {
					if (err) {
						console.log(logSymbols.error + ' pngquant failed to build, make sure that ' + (process.platform === 'darwin' ? 'libpng' : 'libpng-dev') + ' is installed');
						console.log('');
						console.log(err);

						return;
					}

					console.log(logSymbols.success + ' pngquant built successfully!');
				});
			}

			console.log(logSymbols.success + ' pre-build test passed successfully!');
		});
	}
});

/**
 * Module exports
 */

module.exports.path = bin.use();
<|endoftext|>


<commit_before>describe('ng-table', function() {
    var elm, scope, data = [
        {
            id: 1,
            name: "Moroni",
            age: 50,
            money: -10
        },
        {
            id: 2,
            name: "Tiancum",
            age: 43,
            money: 120
        },
        {
            id: 3,
            name: "Jacob",
            age: 27,
            money: 5.5
        },
        {
            id: 4,
            name: "Nephi",
            age: 29,
            money: -54
        },
        {
            id: 5,
            name: "Enos",
            age: 34,
            money: 110
        },
        {
            id: 6,
            name: "Tiancum",
            age: 43,
            money: 1000
        },
        {
            id: 7,
            name: "Jacob",
            age: 27,
            money: -201
        },
        {
            id: 8,
            name: "Nephi",
            age: 29,
            money: 100
        },
        {
            id: 9,
            name: "Enos",
            age: 34,
            money: -52.5
        },
        {
            id: 10,
            name: "Tiancum",
            age: 43,
            money: 52.1
        },
        {
            id: 11,
            name: "Jacob",
            age: 27,
            money: 110
        },
        {
            id: 12,
            name: "Nephi",
            age: 29,
            money: -55
        },
        {
            id: 13,
            name: "Enos",
            age: 34,
            money: 551
        },
        {
            id: 14,
            name: "Tiancum",
            age: 43,
            money: -1410
        },
        {
            id: 15,
            name: "Jacob",
            age: 27,
            money: 410
        },
        {
            id: 16,
            name: "Nephi",
            age: 29,
            money: 100
        },
        {
            id: 17,
            name: "Enos",
            age: 34,
            money: -100
        }
    ];

    beforeEach(module('ngTable'));

    beforeEach(inject(function($rootScope, $compile, $q) {
        elm = angular.element(
            '
' +
            '' +
            '
' +
            '
' +
            '' +
            '{{user.name}}' +
            '	' +
            '' +
            '{{user.age}}' +
            '	' +
            '' +
            '{{user.money}}' +
            '	' +
            '
' +
            '
' +
            '
');

        scope = $rootScope.$new(true);

        scope.money = function() {
            var def = $q.defer();

            def.resolve([{
                'id': 10,
                'title': '10'
            }]);
            return def;
        };

        $compile(elm)(scope);
        scope.$digest();
    }));

    it('should create table header', inject(function($compile, $rootScope) {
        var thead = elm.find('thead');
        expect(thead.length).toBe(1);

        var rows = thead.find('tr');
        expect(rows.length).toBe(2);

        var titles = angular.element(rows[0]).find('th');

        expect(titles.length).toBe(3);
        expect(angular.element(titles[0]).text().trim()).toBe('Name of person');
        expect(angular.element(titles[1]).text().trim()).toBe('Age');
        expect(angular.element(titles[2]).text().trim()).toBe('Money');

        var filters = angular.element(rows[1]).find('th');
        expect(filters.length).toBe(3);
    }));

    it('should show scope data', inject(function($compile, $rootScope, NgTableParams) {
        var tbody = elm.find('tbody');
        expect(tbody.length).toBe(1);

        var rows = tbody.find('tr');
        expect(rows.length).toBe(0);

        var params = new NgTableParams({
            page: 1, // show first page
            count: 10 // count per page
        }, {
            total: data.length, // length of data
            getData: function($defer, params) {
                $defer.resolve(data.slice((params.page() - 1) * params.count(), params.page() * params.count()));
            }
        });
        scope.tableParams = params;
        scope.$digest();

        rows = tbody.find('tr');
        expect(rows.length).toBe(10);

        scope.tableParams.page(2);
        scope.$digest();

        rows = tbody.find('tr');
        expect(rows.length).toBe(7);

        params.total(20);
        scope.$digest();

        rows = tbody.find('tr');
        expect(rows.length).toBe(7);
    }));

});<commit_msg>test(tableSpec): increase test coverage<commit_after>describe('ng-table', function() {
    var elm, scope, data = [
        {
            id: 1,
            name: "Moroni",
            age: 50,
            money: -10
        },
        {
            id: 2,
            name: "Tiancum",
            age: 43,
            money: 120
        },
        {
            id: 3,
            name: "Jacob",
            age: 27,
            money: 5.5
        },
        {
            id: 4,
            name: "Nephi",
            age: 29,
            money: -54
        },
        {
            id: 5,
            name: "Enos",
            age: 34,
            money: 110
        },
        {
            id: 6,
            name: "Tiancum",
            age: 43,
            money: 1000
        },
        {
            id: 7,
            name: "Jacob",
            age: 27,
            money: -201
        },
        {
            id: 8,
            name: "Nephi",
            age: 29,
            money: 100
        },
        {
            id: 9,
            name: "Enos",
            age: 34,
            money: -52.5
        },
        {
            id: 10,
            name: "Tiancum",
            age: 43,
            money: 52.1
        },
        {
            id: 11,
            name: "Jacob",
            age: 27,
            money: 110
        },
        {
            id: 12,
            name: "Nephi",
            age: 29,
            money: -55
        },
        {
            id: 13,
            name: "Enos",
            age: 34,
            money: 551
        },
        {
            id: 14,
            name: "Tiancum",
            age: 43,
            money: -1410
        },
        {
            id: 15,
            name: "Jacob",
            age: 27,
            money: 410
        },
        {
            id: 16,
            name: "Nephi",
            age: 29,
            money: 100
        },
        {
            id: 17,
            name: "Enos",
            age: 34,
            money: -100
        }
    ];

    beforeEach(module('ngTable'));

    beforeEach(inject(function($rootScope, $compile, $q) {
        elm = angular.element(
            '
' +
            '' +
            '
' +
            '
' +
            '' +
            '{{user.name}}' +
            '	' +
            '' +
            '{{user.age}}' +
            '	' +
            '' +
            '{{user.money}}' +
            '	' +
            '
' +
            '
' +
            '
');

        scope = $rootScope.$new(true);

        scope.money = function() {
            var def = $q.defer();

            def.resolve([{
                'id': 10,
                'title': '10'
            }]);
            return def;
        };

        $compile(elm)(scope);
        scope.$digest();
    }));

    it('should create table header', inject(function($compile, $rootScope) {
        var thead = elm.find('thead');
        expect(thead.length).toBe(1);

        var rows = thead.find('tr');
        expect(rows.length).toBe(2);

        var titles = angular.element(rows[0]).find('th');

        expect(titles.length).toBe(3);
        expect(angular.element(titles[0]).text().trim()).toBe('Name of person');
        expect(angular.element(titles[1]).text().trim()).toBe('Age');
        expect(angular.element(titles[2]).text().trim()).toBe('Money');

        var filters = angular.element(rows[1]).find('th');
        expect(filters.length).toBe(3);
    }));

    it('should create table header classes', inject(function($compile, $rootScope) {

        var thead = elm.find('thead');
        var rows = thead.find('tr');
        var titles = angular.element(rows[0]).find('th');

        expect(angular.element(titles[0]).hasClass('header')).toBeTruthy();
        expect(angular.element(titles[1]).hasClass('header')).toBeTruthy();
        expect(angular.element(titles[2]).hasClass('header')).toBeTruthy();
        expect(angular.element(titles[0]).hasClass('sortable')).toBeTruthy();
        expect(angular.element(titles[1]).hasClass('sortable')).toBeTruthy();
        expect(angular.element(titles[2]).hasClass('sortable')).toBeFalsy();
    }));

    it('should show scope data', inject(function($compile, $rootScope, NgTableParams) {
        var tbody = elm.find('tbody');
        expect(tbody.length).toBe(1);

        var rows = tbody.find('tr');
        expect(rows.length).toBe(0);

        var params = new NgTableParams({
            page: 1, // show first page
            count: 10 // count per page
        }, {
            total: data.length, // length of data
            getData: function($defer, params) {
                $defer.resolve(data.slice((params.page() - 1) * params.count(), params.page() * params.count()));
            }
        });
        scope.tableParams = params;
        scope.$digest();

        rows = tbody.find('tr');
        expect(rows.length).toBe(10);

        scope.tableParams.page(2);
        scope.$digest();

        rows = tbody.find('tr');
        expect(rows.length).toBe(7);

        params.total(20);
        scope.$digest();

        rows = tbody.find('tr');
        expect(rows.length).toBe(7);
    }));

});<|endoftext|>