<commit_before>var _ = require('lodash'),
    debug = require('debug')('freecc:cntr:fieldGuide'),
    FieldGuide = require('./../models/FieldGuide'),
    resources = require('./resources'),
    R = require('ramda');

exports.returnIndividualFieldGuide = function(req, res, next) {
    var dashedName = req.params.fieldGuideName;

    var fieldGuideName = dashedName.replace(/\-/g, ' ');

    FieldGuide.find({'name': new RegExp(fieldGuideName, 'i')}, function(err, fieldGuide) {
        if (err) {
            next(err);
        }

        if (fieldGuide.length < 1) {
            req.flash('errors', {
                msg: "404: We couldn't find a field guide entry with that name. Please double check the name."
            });

            return res.redirect('/field-guide');
        }

        fieldGuide = fieldGuide.pop();
        var dashedNameFull = fieldGuide.name.toLowerCase().replace(/\s/g, '-').replace(/\?/g, '');
        if (dashedNameFull != dashedName) {
            return res.redirect('../field-guide/' + dashedNameFull);
        }
        res.render('field-guide/show', {
            title: fieldGuide.name,
            fieldGuideId: fieldGuide._id,
            description: fieldGuide.description.join('')
        });
    });
};

exports.showAllFieldGuides = function(req, res) {
    var data = {};
    data.fieldGuideList = resources.allFieldGuideNames();
    data.fieldGuideIds = resources.allFieldGuideIds();
    if (req.user && req.user.completedFieldGuides) {
      data.completedFieldGuides = req.user.completedFieldGuides;
    } else {
      data.completedFieldGuides = []
    }
    res.send(data);
};

exports.returnNextFieldGuide = function(req, res, next) {
  if (!req.user) {
    return res.redirect('../field-guide/how-do-i-use-this-guide?');
  }

  var completed = req.user.completedFieldGuides;

  req.user.uncompletedFieldGuides = resources.allFieldGuideIds().filter(function (elem) {
    if (completed.indexOf(elem) === -1) {
      return elem;
    }
  });
  req.user.save();

  var uncompletedFieldGuides = req.user.uncompletedFieldGuides;

  var displayedFieldGuides =  FieldGuide.find({'_id': uncompletedFieldGuides[0]});
  displayedFieldGuides.exec(function(err, fieldGuide) {
    if (err) {
      return next(err);
    }
    fieldGuide = fieldGuide.pop();
    if (fieldGuide === undefined) {
      req.flash('success', {
        msg: "You've read all our current Field Guide entries. You can contribute to our Field Guide here."
      });
      return res.redirect('../field-guide/how-do-i-use-this-guide?');
    }
    var nameString = fieldGuide.name.toLowerCase().replace(/\s/g, '-');
    return res.redirect('../field-guide/' + nameString);
  });
};

exports.completedFieldGuide = function (req, res, next) {
  debug('params in completedFieldGuide', req.params);
  var fieldGuideId = req.body.fieldGuideInfo.fieldGuideId;

  req.user.completedFieldGuides.push(fieldGuideId);

  var index = req.user.uncompletedFieldGuides.indexOf(fieldGuideId);
  if (index > -1) {
    req.user.progressTimestamps.push(Date.now() || 0);
    req.user.uncompletedFieldGuides.splice(index, 1);
  }

  req.user.save(function (err, user) {
    if (err) {
      return next(err);
    }
    if (user) {
      res.send(true);
    }
  });
};
<commit_msg>Fix for issue #342: "You can contribute to our field guide here" Page Not Found<commit_after>var _ = require('lodash'),
    debug = require('debug')('freecc:cntr:fieldGuide'),
    FieldGuide = require('./../models/FieldGuide'),
    resources = require('./resources'),
    R = require('ramda');

exports.returnIndividualFieldGuide = function(req, res, next) {
    var dashedName = req.params.fieldGuideName;

    var fieldGuideName = dashedName.replace(/\-/g, ' ');

    FieldGuide.find({'name': new RegExp(fieldGuideName, 'i')}, function(err, fieldGuide) {
        if (err) {
            next(err);
        }

        if (fieldGuide.length < 1) {
            req.flash('errors', {
                msg: "404: We couldn't find a field guide entry with that name. Please double check the name."
            });

            return res.redirect('/field-guide');
        }

        fieldGuide = fieldGuide.pop();
        var dashedNameFull = fieldGuide.name.toLowerCase().replace(/\s/g, '-').replace(/\?/g, '');
        if (dashedNameFull != dashedName) {
            return res.redirect('../field-guide/' + dashedNameFull);
        }
        res.render('field-guide/show', {
            title: fieldGuide.name,
            fieldGuideId: fieldGuide._id,
            description: fieldGuide.description.join('')
        });
    });
};

exports.showAllFieldGuides = function(req, res) {
    var data = {};
    data.fieldGuideList = resources.allFieldGuideNames();
    data.fieldGuideIds = resources.allFieldGuideIds();
    if (req.user && req.user.completedFieldGuides) {
      data.completedFieldGuides = req.user.completedFieldGuides;
    } else {
      data.completedFieldGuides = []
    }
    res.send(data);
};

exports.returnNextFieldGuide = function(req, res, next) {
  if (!req.user) {
    return res.redirect('../field-guide/how-do-i-use-this-guide?');
  }

  var completed = req.user.completedFieldGuides;

  req.user.uncompletedFieldGuides = resources.allFieldGuideIds().filter(function (elem) {
    if (completed.indexOf(elem) === -1) {
      return elem;
    }
  });
  req.user.save();

  var uncompletedFieldGuides = req.user.uncompletedFieldGuides;

  var displayedFieldGuides =  FieldGuide.find({'_id': uncompletedFieldGuides[0]});
  displayedFieldGuides.exec(function(err, fieldGuide) {
    if (err) {
      return next(err);
    }
    fieldGuide = fieldGuide.pop();
    if (fieldGuide === undefined) {
      req.flash('success', {
        msg: "You've read all our current Field Guide entries. You can contribute to our Field Guide here."
      });
      return res.redirect('../field-guide/how-do-i-use-this-guide?');
    }
    var nameString = fieldGuide.name.toLowerCase().replace(/\s/g, '-');
    return res.redirect('../field-guide/' + nameString);
  });
};

exports.completedFieldGuide = function (req, res, next) {
  debug('params in completedFieldGuide', req.params);
  var fieldGuideId = req.body.fieldGuideInfo.fieldGuideId;

  req.user.completedFieldGuides.push(fieldGuideId);

  var index = req.user.uncompletedFieldGuides.indexOf(fieldGuideId);
  if (index > -1) {
    req.user.progressTimestamps.push(Date.now() || 0);
    req.user.uncompletedFieldGuides.splice(index, 1);
  }

  req.user.save(function (err, user) {
    if (err) {
      return next(err);
    }
    if (user) {
      res.send(true);
    }
  });
};
<|endoftext|>


<commit_before>var should   = require("should");
var NodeTree = require("../lib/NodeTree")
var Tree     = require("../lib/Tree")

var root = new NodeTree(null, '', []);

describe('Testing Nodes: ', function() {
  describe('Root element', function() {
    it('Should create a new node', function(){
      root.getFather().should.eql('');
    }),

    it('Is Root?', function() {
      root.isRoot().should.be.true;
    })
  }),

  describe('Seed tree', function() {
    var node = '';
    for (var i = 0; i < 10; i++) {
      node = new NodeTree(i, root, []);
      root.addChild(node);
    };
    it('Children Number', function(){
      root.childrenNumber().should.eql(10);
    }),

    it('Child exists', function() {
      root.existsChild(root.getChild(0)).should.be.true;
    }),

    it('Get child', function() {
      root.getChild(0).should.eql(root.children[0]);
    }),

    it('Get sibling', function() {
      root.firstChild().nextSibling().should.eql(root.getChild(1));
    }),

    it('Remove child', function() {
      root.removeChild(root.firstChild());
      root.childrenNumber().should.eql(9);
    })
  })
})

describe('Testing Tree: ', function() {
  describe('Tree element', function() {
    var root = new NodeTree(null, '', []);
    var tree = new Tree(root);
    var node = '';
    var i, j, k = 0;
    for (i; i < 10; i++) {
      node = new NodeTree(i, root, []);
      tree.getRoot().addChild(node);
    };
    it('Preorder', function() {
      console.log(tree);
    })
  })
})
<commit_msg>created tests for the resource manager<commit_after>var should   = require("should");
var NodeTree = require("../lib/NodeTree");
var Tree     = require("../lib/Tree");
var Resource = require("../lib/Resource");
var ResourceManager = require("../lib/ResourceManager");

var root = new NodeTree(null, '', []);

describe('Testing Nodes: ', function() {
  describe('Root element', function() {
    it('Should create a new node', function(){
      root.getFather().should.eql('');
    }),

    it('Is Root?', function() {
      root.isRoot().should.be.true;
    })
  }),

  describe('Seed tree', function() {
    var node = '';
    for (var i = 0; i < 10; i++) {
      node = new NodeTree(i, root, []);
      root.addChild(node);
    };
    it('Children Number', function(){
      root.childrenNumber().should.eql(10);
    }),

    it('Child exists', function() {
      root.existsChild(root.getChild(0)).should.be.true;
    }),

    it('Get child', function() {
      root.getChild(0).should.eql(root.children[0]);
    }),

    it('Get sibling', function() {
      root.firstChild().nextSibling().should.eql(root.getChild(1));
    }),

    it('Remove child', function() {
      root.removeChild(root.firstChild());
      root.childrenNumber().should.eql(9);
    })
  })
})

describe('Testing Tree: ', function() {
  describe('Tree element', function() {
    var root = new NodeTree('', '', []);
    var tree = new Tree(root);
    var node = '';
    var son  = '';
    var i = 0;
    var j = 0;

    it('Preorder', function() {
      for (i; i < 10; i++) {
        node = new NodeTree(i, root, []);
        tree.getRoot().addChild(node);
        for (j; j < 10; j+=2) {
          son = new NodeTree(j, node, []);
          node.addChild(son);
        }
      };
      tree.preorder(root, console.log);
    })
  })
})

describe('Testing resources: ', function() {
  describe('Loading file', function() {
    var rm = new ResourceManager('test/prueba.txt');
    it('Load File', function() {
      rm.getFileContent()[0].should.eql('first#');
    }),
    it('Read name', function() {
      rm.readName('first#data').should.eql('first');
    }),
    it('Add resource', function() {
      var r = new Resource('name', 'data');
      rm.writeResource(r);
      rm.getFileContent()[1].should.eql('name#data');
    }),
    it('Search resource', function() {
      rm.searchResource('name').should.eql('name#data');
    })
  })
})
<|endoftext|>


<commit_before>/* $This file is distributed under the terms of the license in /doc/license.txt$ */

/* Javascript for sparkline visualization on person profile page */

var visualization = {
	renderCoAuthor: function(url) {

		var containerIdCoAuthor = 'vis_container_coauthor',
		containerCoAuthor = $('#' + containerIdCoAuthor);

        $.ajax({
            url: url,
			data: {
				'render_mode': 'dynamic',
				'vis': 'person_grant_count',
				'vis_mode': 'short',
				'container': containerIdCoAuthor
			},
            dataType: 'html',
            success:function(data){
            	if ($.trim(data) != "") {
            		containerCoAuthor.html(data);
    				containerCoAuthor.children("#pub_count_short_sparkline_vis");
            	}

            }
        });
	}
};

$(document).ready(function() {
    visualization.renderCoAuthor(visualizationUrl);
});<commit_msg>[VIVO-1120] Repair accidental JavaScript commit<commit_after>/* $This file is distributed under the terms of the license in /doc/license.txt$ */

/* Javascript for sparkline visualization on person profile page */

var visualization = {
	renderCoAuthor: function(url) {

		var containerIdCoAuthor = 'vis_container_coauthor',
		containerCoAuthor = $('#' + containerIdCoAuthor);

        $.ajax({
            url: url,
			data: {
				'render_mode': 'dynamic',
				'vis': 'person_pub_count',
				'vis_mode': 'short',
				'container': containerIdCoAuthor
			},
            dataType: 'html',
            success:function(data){
            	if ($.trim(data) != "") {
            		containerCoAuthor.html(data);
    				containerCoAuthor.children("#pub_count_short_sparkline_vis");
            	}

            }
        });
	}
};

$(document).ready(function() {
    visualization.renderCoAuthor(visualizationUrl);
});<|endoftext|>


<commit_before>const { Readable } = require('stream')
const { expect } = require('chai')
const CsvBuilder = require('../lib/CsvBuilder')

class ArrayReadStream extends Readable {
  constructor (options, data) {
    super(options)
    this._index = 0
    this._data = data
  }

  _read () {
    const item = this._index < this._data.length
      ? this._data[this._index++]
      : null
    this.push(item)
  }
}

const collect = stream => new Promise((resolve, reject) => {
  let buf = ''
  stream
    .on('data', chunk => { buf += chunk })
    .on('error', reject)
    .on('end', () => resolve(buf))
})

describe('CsvBuilder', () => {
  describe('Encoding', () => {
    const builder = new CsvBuilder({
      headers: 'foo bar bang'
    })

    const data = {
      foo: 'foo "bar" bang',
      bar: 'bang,baz',
      bang: 42
    }

    it('quotes by default', () => {
      const row = builder.getRow(data)
      expect(row).to.equal(`"foo ""bar"" bang","bang,baz","42"\n`)
    })

    it('does not quote, when "quoted" = false', () => {
      const _builder = new CsvBuilder({
        headers: 'foo bar bang',
        quoted: false
      })
      const row = _builder.getRow(data)
      expect(row).to.equal(`foo "bar" bang,bang,baz,42\n`)
    })

    it('delimter/terminator options', () => {
      const _builder = new CsvBuilder({
        headers: 'foo bar bang',
        delimiter: '\t',
        terminator: '\r\n'
      })
      const row = _builder.getRow(data)
      expect(row).to.equal(`"foo ""bar"" bang"\t"bang,baz"\t"42"\r\n`)
    })

    it('virtuals', () => {
      const _builder = new CsvBuilder({
        headers: 'foo bar bang baz',
        virtuals: {
          baz: () => 'bazz'
        }
      })
        .virtual('foo', data => `${data.foo}|${data.bang}`)

      const row = _builder.getRow(data)
      expect(row).to.equal(`"foo ""bar"" bang|42","bang,baz","42","bazz"\n`)
    })

    it('manual encoding', () => {
      const builder = new CsvBuilder({
        headers: 'foo bar'
      })
      const data = [
        {
          foo: '42',
          bar: 'bang'
        },
        {
          foo: '43',
          bar: 'baz'
        }
      ]

      let csv = builder.getHeaders()
      data.forEach(item => {
        csv += builder.getRow(item)
      })
      expect(csv).to.equal(`"foo","bar"\n"42","bang"\n"43","baz"\n`)
    })
  })

  describe('Streaming', () => {
    const builder = new CsvBuilder({
      headers: 'foo bar bang'
    })

    const data = [
      {
        foo: '"bar"',
        bar: 'bang,baz',
        bang: 42
      },
      {
        foo: 'bang',
        bar: 'bang,"baz"',
        bang: 21
      },
      {
        foo: 'biz',
        bar: void 0,
        bang: null
      }
    ]
    const dataJSON = data.map(item => JSON.stringify(item))
    const expected = [
      `"foo","bar","bang"\n`,
      `"""bar""","bang,baz","42"\n`,
      `"bang","bang,""baz""","21"\n`,
      `"biz","",""\n`
    ].join('')

    it('transform stream (Object Mode)', () => {
      const stream = new ArrayReadStream({ objectMode: true }, data)
      return collect(stream.pipe(builder.createTransformStream()))
        .then(results => {
          expect(results).to.equal(expected)
        })
    })

    it('transform stream (JSON)', () => {
      const stream = new ArrayReadStream({}, dataJSON)
      return collect(stream.pipe(builder.createTransformStream()))
        .then(results => {
          expect(results).to.equal(expected)
        })
    })

    it('creates read streams from data', () => {
      return collect(builder.createReadStream(data))
        .then(results => {
          expect(results).to.equal(expected)
        })
    })
  })

  describe('Complex', () => {
    const builder = new CsvBuilder({
      headers: [
        'First Name',
        'Last Name',
        'Age',
        'Bio',
        'Tags',
        'Likes',
        'Primary Role',
        'Is "active"?'
      ],
      alias: {
        'First Name': 'firstname',
        'Last Name': 'lastname',
        'Age': 'age',
        'Bio': 'bio',
        'Tags': 'tags',
        'Likes': 'likes',
        'Primary Role': 'meta.roles[0]',
        'Is "active"?': 'meta.active'
      }
    })
      .virtual('First Name', user => user.name.split(' ')[0])
      .virtual('Last Name', user => user.name.split(' ')[1])
      .virtual('Tags', user => user.tags.join(','))

    const data = [
      {
        'name': 'User One',
        'age': 42,
        'bio': 'Pellentesque "dapibus" hendrerit tortor.',
        'likes': 'js,go,python',
        'meta': {
          'active': true,
          'roles': [
            'user'
          ]
        },
        'tags': [
          'foo',
          'bar',
          '"bang "'
        ]
      },
      {
        'name': 'User Two',
        'age': 21,
        'bio': 'Nam "eget dui".',
        'likes': 'graphql,number theory',
        'meta': {
          'active': false,
          'roles': [
            'admin',
            'user'
          ]
        },
        'tags': [
          'foo',
          'bar',
          'baz'
        ]
      }
    ]

    const expected = [
      `"First Name","Last Name","Age","Bio","Tags","Likes","Primary Role","Is ""active""?"\n`,
      `"User","One","42","Pellentesque ""dapibus"" hendrerit tortor.","foo,bar,""bang ""","js,go,python","user","true"\n`,
      `"User","Two","21","Nam ""eget dui"".","foo,bar,baz","graphql,number theory","admin","false"\n`
    ].join('')

    it('handles complex schemas', () => {
      return collect(builder.createReadStream(data))
        .then(results => {
          expect(results).to.equal(expected)
        })
    })
  })
})
<commit_msg>Add test for other non-truthy values<commit_after>const { Readable } = require('stream')
const { expect } = require('chai')
const CsvBuilder = require('../lib/CsvBuilder')

class ArrayReadStream extends Readable {
  constructor (options, data) {
    super(options)
    this._index = 0
    this._data = data
  }

  _read () {
    const item = this._index < this._data.length
      ? this._data[this._index++]
      : null
    this.push(item)
  }
}

const collect = stream => new Promise((resolve, reject) => {
  let buf = ''
  stream
    .on('data', chunk => { buf += chunk })
    .on('error', reject)
    .on('end', () => resolve(buf))
})

describe('CsvBuilder', () => {
  describe('Encoding', () => {
    const builder = new CsvBuilder({
      headers: 'foo bar bang'
    })

    const data = {
      foo: 'foo "bar" bang',
      bar: 'bang,baz',
      bang: 42
    }

    it('quotes by default', () => {
      const row = builder.getRow(data)
      expect(row).to.equal(`"foo ""bar"" bang","bang,baz","42"\n`)
    })

    it('does not quote, when "quoted" = false', () => {
      const _builder = new CsvBuilder({
        headers: 'foo bar bang',
        quoted: false
      })
      const row = _builder.getRow(data)
      expect(row).to.equal(`foo "bar" bang,bang,baz,42\n`)
    })

    it('delimter/terminator options', () => {
      const _builder = new CsvBuilder({
        headers: 'foo bar bang',
        delimiter: '\t',
        terminator: '\r\n'
      })
      const row = _builder.getRow(data)
      expect(row).to.equal(`"foo ""bar"" bang"\t"bang,baz"\t"42"\r\n`)
    })

    it('virtuals', () => {
      const _builder = new CsvBuilder({
        headers: 'foo bar bang baz',
        virtuals: {
          baz: () => 'bazz'
        }
      })
        .virtual('foo', data => `${data.foo}|${data.bang}`)

      const row = _builder.getRow(data)
      expect(row).to.equal(`"foo ""bar"" bang|42","bang,baz","42","bazz"\n`)
    })

    it('manual encoding', () => {
      const builder = new CsvBuilder({
        headers: 'foo bar'
      })
      const data = [
        {
          foo: '42',
          bar: 'bang'
        },
        {
          foo: '43',
          bar: 'baz'
        }
      ]

      let csv = builder.getHeaders()
      data.forEach(item => {
        csv += builder.getRow(item)
      })
      expect(csv).to.equal(`"foo","bar"\n"42","bang"\n"43","baz"\n`)
    })
  })

  describe('Streaming', () => {
    const builder = new CsvBuilder({
      headers: 'foo bar bang'
    })

    const data = [
      {
        foo: '"bar"',
        bar: 'bang,baz',
        bang: 42
      },
      {
        foo: 'bang',
        bar: 'bang,"baz"',
        bang: 21
      },
      {
        foo: 0,
        bar: void 0,
        bang: null
      }
    ]
    const dataJSON = data.map(item => JSON.stringify(item))
    const expected = [
      `"foo","bar","bang"\n`,
      `"""bar""","bang,baz","42"\n`,
      `"bang","bang,""baz""","21"\n`,
      `"0","",""\n`
    ].join('')

    it('transform stream (Object Mode)', () => {
      const stream = new ArrayReadStream({ objectMode: true }, data)
      return collect(stream.pipe(builder.createTransformStream()))
        .then(results => {
          expect(results).to.equal(expected)
        })
    })

    it('transform stream (JSON)', () => {
      const stream = new ArrayReadStream({}, dataJSON)
      return collect(stream.pipe(builder.createTransformStream()))
        .then(results => {
          expect(results).to.equal(expected)
        })
    })

    it('creates read streams from data', () => {
      return collect(builder.createReadStream(data))
        .then(results => {
          expect(results).to.equal(expected)
        })
    })
  })

  describe('Complex', () => {
    const builder = new CsvBuilder({
      headers: [
        'First Name',
        'Last Name',
        'Age',
        'Bio',
        'Tags',
        'Likes',
        'Primary Role',
        'Is "active"?'
      ],
      alias: {
        'First Name': 'firstname',
        'Last Name': 'lastname',
        'Age': 'age',
        'Bio': 'bio',
        'Tags': 'tags',
        'Likes': 'likes',
        'Primary Role': 'meta.roles[0]',
        'Is "active"?': 'meta.active'
      }
    })
      .virtual('First Name', user => user.name.split(' ')[0])
      .virtual('Last Name', user => user.name.split(' ')[1])
      .virtual('Tags', user => user.tags.join(','))

    const data = [
      {
        'name': 'User One',
        'age': 42,
        'bio': 'Pellentesque "dapibus" hendrerit tortor.',
        'likes': 'js,go,python',
        'meta': {
          'active': true,
          'roles': [
            'user'
          ]
        },
        'tags': [
          'foo',
          'bar',
          '"bang "'
        ]
      },
      {
        'name': 'User Two',
        'age': 21,
        'bio': 'Nam "eget dui".',
        'likes': 'graphql,number theory',
        'meta': {
          'active': false,
          'roles': [
            'admin',
            'user'
          ]
        },
        'tags': [
          'foo',
          'bar',
          'baz'
        ]
      }
    ]

    const expected = [
      `"First Name","Last Name","Age","Bio","Tags","Likes","Primary Role","Is ""active""?"\n`,
      `"User","One","42","Pellentesque ""dapibus"" hendrerit tortor.","foo,bar,""bang ""","js,go,python","user","true"\n`,
      `"User","Two","21","Nam ""eget dui"".","foo,bar,baz","graphql,number theory","admin","false"\n`
    ].join('')

    it('handles complex schemas', () => {
      return collect(builder.createReadStream(data))
        .then(results => {
          expect(results).to.equal(expected)
        })
    })
  })
})
<|endoftext|>


<commit_before>import React, {Component, PropTypes} from 'react';

import ReactNative, {
  View,
  ListView
} from 'react-native';

import shallowCompare from 'react-addons-shallow-compare';

import InvertibleScrollView from 'react-native-invertible-scroll-view';

import LoadEarlier from './LoadEarlier';
import Message from './Message';

import md5 from 'md5';

export default class MessageContainer extends Component {
  constructor(props) {
    super(props);

    this.renderRow = this.renderRow.bind(this);
    this.renderFooter = this.renderFooter.bind(this);
    this.renderLoadEarlier = this.renderLoadEarlier.bind(this);
    this.renderScrollComponent = this.renderScrollComponent.bind(this);

    const dataSource = new ListView.DataSource({
      rowHasChanged: (r1, r2) => r1.hash !== r2.hash
    });

    const messagesData = this.prepareMessages(props.messages);
    this.state = {
      dataSource: dataSource.cloneWithRows(messagesData.blob, messagesData.keys)
    };
  }

  prepareMessages(messages) {
    return {
      keys: messages.map(m => m._id),
      blob: messages.reduce((o, m) => {
        o[m._id] = {
          ...m,
          hash: md5(JSON.stringify(m))
        };
        return o;
      }, {})
    };
  }

  shouldComponentUpdate(nextProps, nextState) {
    return shallowCompare(this, nextProps, nextState);
  }

  componentWillReceiveProps(nextProps) {
    if (this.props.messages === nextProps.messages) {
      return;
    }
    const messagesData = this.prepareMessages(nextProps.messages);
    this.setState({
      dataSource: this.state.dataSource.cloneWithRows(messagesData.blob, messagesData.keys)
    });
  }

  renderFooter() {
    if (this.props.renderFooter) {
      const footerProps = {
        ...this.props,
      };
      return this.props.renderFooter(footerProps);
    }
    return null;
  }

  renderLoadEarlier() {
    if (this.props.loadEarlier === true) {
      const loadEarlierProps = {
        ...this.props,
      };
      if (this.props.renderLoadEarlier) {
        return this.props.renderLoadEarlier(loadEarlierProps);
      }
      return (
        <LoadEarlier {...loadEarlierProps}/>
      );
    }
    return null;
  }

  scrollTo(options) {
    this._invertibleScrollViewRef.scrollTo(options);
  }

  renderRow(message, sectionId, rowId) {
    if (!message._id) {
      console.warn('GiftedChat: `_id` is missing for message', JSON.stringify(message));
    }
    if (!message.user) {
      console.warn('GiftedChat: `user` is missing for message', JSON.stringify(message));
      message.user = {};
    }

    const index = Number(rowId);

    const messageProps = {
      ...this.props,
      key: message._id,
      currentMessage: message,
      previousMessage: this.props.messages[index + 1] || {},
      nextMessage: this.props.messages[index - 1] || {},
      position: message.user._id === this.props.user._id ? 'right' : 'left',
    };

    if (this.props.renderMessage) {
      return this.props.renderMessage(messageProps);
    }
    return <Message {...messageProps}/>;
  }

  renderScrollComponent(props) {
    const invertibleScrollViewProps = this.props.invertibleScrollViewProps;
    return (
      <InvertibleScrollView
        {...invertibleScrollViewProps}
        ref={component => this._invertibleScrollViewRef = component}
      />
    );
  }

  render() {
    return (
      <View ref='container' style={{flex:1}}>
        <ListView
          enableEmptySections={true}
          keyboardShouldPersistTaps={true}
          automaticallyAdjustContentInsets={false}
          initialListSize={20}
          pageSize={20}

          dataSource={this.state.dataSource}

          renderRow={this.renderRow}
          renderHeader={this.renderFooter}
          renderFooter={this.renderLoadEarlier}
          renderScrollComponent={this.renderScrollComponent}
        />
      </View>
    );
  }
}

MessageContainer.defaultProps = {
  messages: [],
  user: {},
  renderFooter: null,
  renderMessage: null,
  onLoadEarlier: () => {
  },
};<commit_msg>Pass props from renderScrollContainer to InvertibleScrollView to fix not rendering more then (initial + pageSize) rows.<commit_after>import React, {Component, PropTypes} from 'react';

import ReactNative, {
  View,
  ListView
} from 'react-native';

import shallowCompare from 'react-addons-shallow-compare';

import InvertibleScrollView from 'react-native-invertible-scroll-view';

import LoadEarlier from './LoadEarlier';
import Message from './Message';

import md5 from 'md5';

export default class MessageContainer extends Component {
  constructor(props) {
    super(props);

    this.renderRow = this.renderRow.bind(this);
    this.renderFooter = this.renderFooter.bind(this);
    this.renderLoadEarlier = this.renderLoadEarlier.bind(this);
    this.renderScrollComponent = this.renderScrollComponent.bind(this);

    const dataSource = new ListView.DataSource({
      rowHasChanged: (r1, r2) => r1.hash !== r2.hash
    });

    const messagesData = this.prepareMessages(props.messages);
    this.state = {
      dataSource: dataSource.cloneWithRows(messagesData.blob, messagesData.keys)
    };
  }

  prepareMessages(messages) {
    return {
      keys: messages.map(m => m._id),
      blob: messages.reduce((o, m) => {
        o[m._id] = {
          ...m,
          hash: md5(JSON.stringify(m))
        };
        return o;
      }, {})
    };
  }

  shouldComponentUpdate(nextProps, nextState) {
    return shallowCompare(this, nextProps, nextState);
  }

  componentWillReceiveProps(nextProps) {
    if (this.props.messages === nextProps.messages) {
      return;
    }
    const messagesData = this.prepareMessages(nextProps.messages);
    this.setState({
      dataSource: this.state.dataSource.cloneWithRows(messagesData.blob, messagesData.keys)
    });
  }

  renderFooter() {
    if (this.props.renderFooter) {
      const footerProps = {
        ...this.props,
      };
      return this.props.renderFooter(footerProps);
    }
    return null;
  }

  renderLoadEarlier() {
    if (this.props.loadEarlier === true) {
      const loadEarlierProps = {
        ...this.props,
      };
      if (this.props.renderLoadEarlier) {
        return this.props.renderLoadEarlier(loadEarlierProps);
      }
      return (
        <LoadEarlier {...loadEarlierProps}/>
      );
    }
    return null;
  }

  scrollTo(options) {
    this._invertibleScrollViewRef.scrollTo(options);
  }

  renderRow(message, sectionId, rowId) {
    if (!message._id) {
      console.warn('GiftedChat: `_id` is missing for message', JSON.stringify(message));
    }
    if (!message.user) {
      console.warn('GiftedChat: `user` is missing for message', JSON.stringify(message));
      message.user = {};
    }

    const index = Number(rowId);

    const messageProps = {
      ...this.props,
      key: message._id,
      currentMessage: message,
      previousMessage: this.props.messages[index + 1] || {},
      nextMessage: this.props.messages[index - 1] || {},
      position: message.user._id === this.props.user._id ? 'right' : 'left',
    };

    if (this.props.renderMessage) {
      return this.props.renderMessage(messageProps);
    }
    return <Message {...messageProps}/>;
  }

  renderScrollComponent(props) {
    const invertibleScrollViewProps = this.props.invertibleScrollViewProps;
    return (
      <InvertibleScrollView
        {...props}
        {...invertibleScrollViewProps}
        ref={component => this._invertibleScrollViewRef = component}
      />
    );
  }

  render() {
    return (
      <View ref='container' style={{flex:1}}>
        <ListView
          enableEmptySections={true}
          keyboardShouldPersistTaps={true}
          automaticallyAdjustContentInsets={false}
          initialListSize={20}
          pageSize={20}

          dataSource={this.state.dataSource}

          renderRow={this.renderRow}
          renderHeader={this.renderFooter}
          renderFooter={this.renderLoadEarlier}
          renderScrollComponent={this.renderScrollComponent}
        />
      </View>
    );
  }
}

MessageContainer.defaultProps = {
  messages: [],
  user: {},
  renderFooter: null,
  renderMessage: null,
  onLoadEarlier: () => {
  },
};<|endoftext|>


<commit_before>'use strict'

var Trello = require('node-trello')
var Promise = require('bluebird')
var moment = require('moment')
var _ = require('lodash')

module.exports = function initializeModule(options) {
  var t = Promise.promisifyAll(new Trello(options.trelloKey, options.trelloToken))

  function findBoard (boardName) {
    return Promise.try(function () {
      return t.getAsync('/1/members/me')
    }).then(function (data) {
      return data.idBoards
    }).map(function (boardId) {
      return t.getAsync('/1/boards/' + boardId, {lists: 'all'})
    }).filter(function (board) {
      return board.name === boardName
    }).then(function (boards) {
      if (boardName === undefined) {
        throw new Error('boardName not set in ENV')
      } else if (boards.length === 0) {
        throw new Error('No results found.')
      } else {
        return boards[0]
      }
    })
  }

  function getOrCreateList (board, listName) {
    return Promise.filter(board.lists, function (list) {
      return list.name === listName
    }).then(function (lists) {
      if (lists.length > 0) {
        return lists[0]
      } else {
        return createList({
          name: listName,
          board: board,
          position: 'top'
        })
      }
    })
  }

  function createList (opts) {
    return Promise.try(function () {
      return t.postAsync('/1/lists', {
        name: opts.name,
        idBoard: opts.board,
        idListSource: opts.sourceList,
        position: opts.position
      })
    })
  }

  function init (boardName) {
    return Promise.try(function () {
      return findBoard(boardName)
    }).then(function (board) {
      return Promise.all([
        getOrCreateList(board, 'Today'),
        getOrCreateList(board, 'Daily Processes')
      ]).spread(function (targetList, sourceList) {
        return {
          boardId: board.id,
          sourceList: sourceList.id,
          targetList: targetList.id
        }
      })
    })
  }

  var dailyLabel = options.trelloLabel

  function createToday () {
    return Promise.try(function () {
      return init(options.trelloBoard)
    }).then(function (result) {
      Promise.try(function () {
        return createList({
          name: moment().format('MMMM Do, YYYY'),
          board: result.boardId,
          sourceList: result.sourceList,
          position: '3'
        })
      }).then(function (list) {
        return t.postAsync('/1/lists/' + list.id + '/moveAllCards', {
          idBoard: result.boardId,
          idList: result.targetList
        })
      }).then(function () {
        console.log('Done')
      })
    })
  }

  function removeDuplicates (listId, dailyOnly) {
    return Promise.try(function () {
      return init(options.trelloBoard)
    }).then(function (result) {
      if (listId == null) {
        listId = result.targetList
      }

      if (dailyOnly == null) {
        dailyOnly = true
      }

      return Promise.try(function () {
        return t.getAsync('/1/lists/' + listId, {cards: 'open'})
      }).then(function (result) {
        return _.difference(result.cards, _.uniq(result.cards, 'name'))
      }).filter(function (card) {
        return (dailyOnly && _.includes(card.idLabels, dailyLabel))
      }).map(function (card) {
        return Promise.try(function () {
          return t.delAsync('/1/cards/' + card.id)
        }).then(function () {
          console.log('Deleted card: ' + card.name + ' (' + card.id + ')')
        })
      })
    })
  }

  return {
    createToday: createToday,
    removeDuplicates: removeDuplicates
  }
}
<commit_msg>Moved cards to top of list<commit_after>'use strict'

var Trello = require('node-trello')
var Promise = require('bluebird')
var moment = require('moment')
var _ = require('lodash')

module.exports = function initializeModule(options) {
  var t = Promise.promisifyAll(new Trello(options.trelloKey, options.trelloToken))

  function findBoard (boardName) {
    return Promise.try(function () {
      return t.getAsync('/1/members/me')
    }).then(function (data) {
      return data.idBoards
    }).map(function (boardId) {
      return t.getAsync('/1/boards/' + boardId, {lists: 'all'})
    }).filter(function (board) {
      return board.name === boardName
    }).then(function (boards) {
      if (boardName === undefined) {
        throw new Error('boardName not set in ENV')
      } else if (boards.length === 0) {
        throw new Error('No results found.')
      } else {
        return boards[0]
      }
    })
  }

  function getOrCreateList (board, listName) {
    return Promise.filter(board.lists, function (list) {
      return list.name === listName
    }).then(function (lists) {
      if (lists.length > 0) {
        return lists[0]
      } else {
        return createList({
          name: listName,
          board: board,
          position: 'top'
        })
      }
    })
  }

  function createList (opts) {
    return Promise.try(function () {
      return t.postAsync('/1/lists', {
        name: opts.name,
        idBoard: opts.board,
        idListSource: opts.sourceList,
        position: opts.position
      })
    })
  }

  function moveCardPosition (opts) {
    return Promise.try(function () {
      return t.getAsync('/1/lists/' + opts.targetList, {cards: 'open'})
    }).then(function (result) {
      return result.cards
    }).filter(function (card) {
      return (opts.dailyOnly && _.includes(card.idLabels, opts.dailyLabel))
    }).map(function (card) {
      return Promise.try(function () {
        return t.putAsync('/1/cards/' + card.id, {pos: opts.pos})
      }).then(function () {
        console.log('Copied card: ' + card.name)
      })
    })
  }

  function init (boardName) {
    return Promise.try(function () {
      return findBoard(boardName)
    }).then(function (board) {
      return Promise.all([
        getOrCreateList(board, 'Today'),
        getOrCreateList(board, 'Daily Processes')
      ]).spread(function (targetList, sourceList) {
        return {
          boardId: board.id,
          sourceList: sourceList.id,
          targetList: targetList.id
        }
      })
    })
  }

  var dailyLabel = options.trelloLabel

  function createToday () {
    return Promise.try(function () {
      return init(options.trelloBoard)
    }).then(function (result) {
      Promise.try(function () {
        return createList({
          name: moment().format('MMMM Do, YYYY'),
          board: result.boardId,
          sourceList: result.sourceList,
          position: '3'
        })
      }).then(function (list) {
        return t.postAsync('/1/lists/' + list.id + '/moveAllCards', {
          idBoard: result.boardId,
          idList: result.targetList
        })
      }).then(function () {
        return moveCardPosition({
          targetList: result.targetList,
          dailyLabel: dailyLabel,
          dailyOnly: true,
          pos: 'top'
        })
      }).then(function () {
        console.log('Done')
      })
    })
  }

  function removeDuplicates (listId, dailyOnly) {
    return Promise.try(function () {
      return init(options.trelloBoard)
    }).then(function (result) {
      if (listId == null) {
        listId = result.targetList
      }

      if (dailyOnly == null) {
        dailyOnly = true
      }

      return Promise.try(function () {
        return t.getAsync('/1/lists/' + listId, {cards: 'open'})
      }).then(function (result) {
        return _.difference(result.cards, _.uniq(result.cards, 'name'))
      }).filter(function (card) {
        return (dailyOnly && _.includes(card.idLabels, dailyLabel))
      }).map(function (card) {
        return Promise.try(function () {
          return t.delAsync('/1/cards/' + card.id)
        }).then(function () {
          console.log('Deleted card: ' + card.name + ' (' + card.id + ')')
        })
      })
    })
  }

  return {
    createToday: createToday,
    removeDuplicates: removeDuplicates
  }
}
<|endoftext|>


<commit_before>/*global require, suite, setup, teardown, test */

(function () {
    'use strict';

    var assert = require('chai').assert,

    modulePath = '../src/complexityReport';

    suite('complexityReport:', function () {
        test('require does not throw', function () {
            assert.doesNotThrow(function () {
                require(modulePath);
            });
        });

        test('require returns object', function () {
            assert.isObject(require(modulePath));
        });

        suite('require:', function () {
            var cr;

            setup(function () {
                cr = require(modulePath);
            });

            teardown(function () {
                cr = undefined;
            });

            test('run function is exported', function () {
                assert.isFunction(cr.run);
            });

            test('run throws when source is object', function () {
                assert.throws(function () {
                    cr.run({});
                });
            });

            test('run throws when source is empty string', function () {
                assert.throws(function () {
                    cr.run('');
                });
            });

            test('run throws when source is not javascript', function () {
                assert.throws(function () {
                    cr.run('foo bar');
                });
            });

            test('run does not throw when source is javascript', function () {
                assert.doesNotThrow(function () {
                    cr.run('console.log("foo");');
                });
            });

            test('run returns object', function () {
                assert.isObject(cr.run('"foo"'));
            });

            test('run returns aggregate complexity property', function () {
                assert.isObject(cr.run('"foo"').aggregate.complexity);
            });

            test('run returns functions property', function () {
                assert.isArray(cr.run('"foo"').functions);
            });

            suite('run against function call:', function () {
                var report;

                setup(function () {
                    report = cr.run('parseInt("10", 10);');
                });

                teardown(function () {
                    report = undefined;
                });

                test('aggregate has correct cyclomatic complexity', function () {
                    assert.strictEqual(report.aggregate.complexity.cyclomatic, 1);
                });

                test('functions is empty', function () {
                    assert.lengthOf(report.functions, 0);
                });
            });

            suite('run against condition:', function () {
                var report;

                setup(function () {
                    report = cr.run('if (true) { "foo"; }');
                });

                teardown(function () {
                    report = undefined;
                });

                test('aggregate has correct cyclomatic complexity', function () {
                    assert.strictEqual(report.aggregate.complexity.cyclomatic, 2);
                });

                test('functions is empty', function () {
                    assert.lengthOf(report.functions, 0);
                });
            });

            suite('run against condition with alternate:', function () {
                var report;

                setup(function () {
                    report = cr.run('if (true) { "foo"; } else { "bar"; }');
                });

                teardown(function () {
                    report = undefined;
                });

                test('aggregate has correct cyclomatic complexity', function () {
                    assert.strictEqual(report.aggregate.complexity.cyclomatic, 2);
                });

                test('functions is empty', function () {
                    assert.lengthOf(report.functions, 0);
                });
            });

            suite('run against dual condition:', function () {
                var report;

                setup(function () {
                    report = cr.run('if (true) { "foo"; } if (false) { "bar"; }');
                });

                teardown(function () {
                    report = undefined;
                });

                test('aggregate has correct cyclomatic complexity', function () {
                    assert.strictEqual(report.aggregate.complexity.cyclomatic, 3);
                });
            });

            suite('run against alternate dual condition:', function () {
                var report;

                setup(function () {
                    report = cr.run('if (true) { "foo"; } else if (false) { "bar"; }');
                });

                teardown(function () {
                    report = undefined;
                });

                test('aggregate has correct cyclomatic complexity', function () {
                    assert.strictEqual(report.aggregate.complexity.cyclomatic, 3);
                });
            });

            suite('run against nested condition:', function () {
                var report;

                setup(function () {
                    report = cr.run('if (true) { "foo"; if (false) { "bar"; } }');
                });

                teardown(function () {
                    report = undefined;
                });

                test('aggregate has correct cyclomatic complexity', function () {
                    assert.strictEqual(report.aggregate.complexity.cyclomatic, 3);
                });
            });

            suite('run against function declaration:', function () {
                var report;

                setup(function () {
                    report = cr.run('function foo () { "bar"; }');
                });

                teardown(function () {
                    report = undefined;
                });

                test('aggregate has correct cyclomatic complexity', function () {
                    assert.strictEqual(report.aggregate.complexity.cyclomatic, 1);
                });

                test('functions has correct length', function () {
                    assert.lengthOf(report.functions, 1);
                });

                test('function has correct name', function () {
                    assert.strictEqual(report.functions[0].name, 'foo');
                });

                test('function has complexity property', function () {
                    assert.isObject(report.functions[0].complexity);
                });

                test('function has correct cyclomatic complexity', function () {
                    assert.strictEqual(report.functions[0].complexity.cyclomatic, 1);
                });
            });

            suite('run against nested function declaration:', function () {
                var report;

                setup(function () {
                    report = cr.run('function foo () { bar(); function bar () { "baz"; } }');
                });

                teardown(function () {
                    report = undefined;
                });

                test('functions has correct length', function () {
                    assert.lengthOf(report.functions, 2);
                });

                test('first function has correct name', function () {
                    assert.strictEqual(report.functions[0].name, 'foo');
                });

                test('second function has correct name', function () {
                    assert.strictEqual(report.functions[1].name, 'bar');
                });
            });

            suite('run against function declaration containing conditional:', function () {
                var report;

                setup(function () {
                    report = cr.run('function foo () { if (true) { "bar"; } }');
                });

                teardown(function () {
                    report = undefined;
                });

                test('aggregate has correct cyclomatic complexity', function () {
                    assert.strictEqual(report.aggregate.complexity.cyclomatic, 2);
                });

                test('function has correct cyclomatic complexity', function () {
                    assert.strictEqual(report.functions[0].complexity.cyclomatic, 2);
                });
            });

            suite('run against assignment expression', function () {
                var report;

                setup(function () {
                    report = cr.run('var foo = "bar";');
                });

                teardown(function () {
                    report = undefined;
                });

                test('aggregate has correct cyclomatic complexity', function () {
                    assert.strictEqual(report.aggregate.complexity.cyclomatic, 1);
                });

                test('functions is empty', function () {
                    assert.lengthOf(report.functions, 0);
                });
            });

            suite('run against ternary condtional expression assigned to variable', function () {
                var report;

                setup(function () {
                    report = cr.run('var foo = true ? "bar" : "baz";');
                });

                teardown(function () {
                    report = undefined;
                });

                test('aggregate has correct cyclomatic complexity', function () {
                    assert.strictEqual(report.aggregate.complexity.cyclomatic, 2);
                });
            });

            suite('run against logical or expression assigned to variable', function () {
                var report;

                setup(function () {
                    report = cr.run('var foo = true || false;');
                });

                teardown(function () {
                    report = undefined;
                });

                test('aggregate has correct cyclomatic complexity', function () {
                    assert.strictEqual(report.aggregate.complexity.cyclomatic, 2);
                });
            });

            suite('run against anonymous function assigned to variable:', function () {
                var report;

                setup(function () {
                    report = cr.run('var foo = function () { "bar"; }');
                });

                teardown(function () {
                    report = undefined;
                });

                test('functions has correct length', function () {
                    assert.lengthOf(report.functions, 1);
                });

                test('function has correct name', function () {
                    assert.strictEqual(report.functions[0].name, 'foo');
                });
            });

            suite('run against named function assigned to variable:', function () {
                var report;

                setup(function () {
                    report = cr.run('var foo = function bar () { "baz"; }');
                });

                teardown(function () {
                    report = undefined;
                });

                test('function has correct name', function () {
                    assert.strictEqual(report.functions[0].name, 'bar');
                });
            });

            suite('run against ternary condtional expression returned from function', function () {
                var report;

                setup(function () {
                    report = cr.run('function foo () { return true ? "bar" : "baz"; }');
                });

                teardown(function () {
                    report = undefined;
                });

                test('aggregate has correct cyclomatic complexity', function () {
                    assert.strictEqual(report.aggregate.complexity.cyclomatic, 2);
                });

                test('function has correct cyclomatic complexity', function () {
                    assert.strictEqual(report.functions[0].complexity.cyclomatic, 2);
                });
            });

            suite('run against logical or expression returned from function', function () {
                var report;

                setup(function () {
                    report = cr.run('function foo () { return true || false; }');
                });

                teardown(function () {
                    report = undefined;
                });

                test('aggregate has correct cyclomatic complexity', function () {
                    assert.strictEqual(report.aggregate.complexity.cyclomatic, 2);
                });

                test('function has correct cyclomatic complexity', function () {
                    assert.strictEqual(report.functions[0].complexity.cyclomatic, 2);
                });
            });

            suite('run against anonymous function returned from function:', function () {
                var report;

                setup(function () {
                    report = cr.run('function foo () { return function () { "bar"; }; }');
                });

                teardown(function () {
                    report = undefined;
                });

                test('functions has correct length', function () {
                    assert.lengthOf(report.functions, 2);
                });

                test('first function has correct name', function () {
                    assert.strictEqual(report.functions[0].name, 'foo');
                });

                test('second function is anonymous', function () {
                    assert.isUndefined(report.functions[1].name);
                });
            });

            suite('run against named function returned from function:', function () {
                var report;

                setup(function () {
                    report = cr.run('function foo () { return function bar () { "baz"; }; }');
                });

                teardown(function () {
                    report = undefined;
                });

                test('second function has correct name', function () {
                    assert.strictEqual(report.functions[1].name, 'bar');
                });
            });

            suite('run against anonymous function passed as argument:', function () {
                var report;

                setup(function () {
                    report = cr.run('setTimeout(function () { "foo"; }, 1000);');
                });

                teardown(function () {
                    report = undefined;
                });

                test('functions has correct length', function () {
                    assert.lengthOf(report.functions, 1);
                });

                test('function is anonymous', function () {
                    assert.isUndefined(report.functions[0].name);
                });
            });

            suite('run against named function passed as argument:', function () {
                var report;

                setup(function () {
                    report = cr.run('setTimeout(function foo () { "bar"; }, 1000);');
                });

                teardown(function () {
                    report = undefined;
                });

                test('function has correct name', function () {
                    assert.strictEqual(report.functions[0].name, 'foo');
                });
            });
        });
    });
}());

<commit_msg>More unit tests.<commit_after>/*global require, suite, setup, teardown, test */

(function () {
    'use strict';

    var assert = require('chai').assert,

    modulePath = '../src/complexityReport';

    suite('complexityReport:', function () {
        test('require does not throw', function () {
            assert.doesNotThrow(function () {
                require(modulePath);
            });
        });

        test('require returns object', function () {
            assert.isObject(require(modulePath));
        });

        suite('require:', function () {
            var cr;

            setup(function () {
                cr = require(modulePath);
            });

            teardown(function () {
                cr = undefined;
            });

            test('run function is exported', function () {
                assert.isFunction(cr.run);
            });

            test('run throws when source is object', function () {
                assert.throws(function () {
                    cr.run({});
                });
            });

            test('run throws when source is empty string', function () {
                assert.throws(function () {
                    cr.run('');
                });
            });

            test('run throws when source is not javascript', function () {
                assert.throws(function () {
                    cr.run('foo bar');
                });
            });

            test('run does not throw when source is javascript', function () {
                assert.doesNotThrow(function () {
                    cr.run('console.log("foo");');
                });
            });

            test('run returns object', function () {
                assert.isObject(cr.run('"foo"'));
            });

            test('run returns aggregate complexity property', function () {
                assert.isObject(cr.run('"foo"').aggregate.complexity);
            });

            test('run returns functions property', function () {
                assert.isArray(cr.run('"foo"').functions);
            });

            suite('run against function call:', function () {
                var report;

                setup(function () {
                    report = cr.run('parseInt("10", 10);');
                });

                teardown(function () {
                    report = undefined;
                });

                test('aggregate has correct cyclomatic complexity', function () {
                    assert.strictEqual(report.aggregate.complexity.cyclomatic, 1);
                });

                test('functions is empty', function () {
                    assert.lengthOf(report.functions, 0);
                });
            });

            suite('run against condition:', function () {
                var report;

                setup(function () {
                    report = cr.run('if (true) { "foo"; }');
                });

                teardown(function () {
                    report = undefined;
                });

                test('aggregate has correct cyclomatic complexity', function () {
                    assert.strictEqual(report.aggregate.complexity.cyclomatic, 2);
                });

                test('functions is empty', function () {
                    assert.lengthOf(report.functions, 0);
                });
            });

            suite('run against condition with alternate:', function () {
                var report;

                setup(function () {
                    report = cr.run('if (true) { "foo"; } else { "bar"; }');
                });

                teardown(function () {
                    report = undefined;
                });

                test('aggregate has correct cyclomatic complexity', function () {
                    assert.strictEqual(report.aggregate.complexity.cyclomatic, 2);
                });

                test('functions is empty', function () {
                    assert.lengthOf(report.functions, 0);
                });
            });

            suite('run against dual condition:', function () {
                var report;

                setup(function () {
                    report = cr.run('if (true) { "foo"; } if (false) { "bar"; }');
                });

                teardown(function () {
                    report = undefined;
                });

                test('aggregate has correct cyclomatic complexity', function () {
                    assert.strictEqual(report.aggregate.complexity.cyclomatic, 3);
                });
            });

            suite('run against alternate dual condition:', function () {
                var report;

                setup(function () {
                    report = cr.run('if (true) { "foo"; } else if (false) { "bar"; }');
                });

                teardown(function () {
                    report = undefined;
                });

                test('aggregate has correct cyclomatic complexity', function () {
                    assert.strictEqual(report.aggregate.complexity.cyclomatic, 3);
                });
            });

            suite('run against nested condition:', function () {
                var report;

                setup(function () {
                    report = cr.run('if (true) { "foo"; if (false) { "bar"; } }');
                });

                teardown(function () {
                    report = undefined;
                });

                test('aggregate has correct cyclomatic complexity', function () {
                    assert.strictEqual(report.aggregate.complexity.cyclomatic, 3);
                });
            });

            suite('run against function declaration:', function () {
                var report;

                setup(function () {
                    report = cr.run('function foo () { "bar"; }');
                });

                teardown(function () {
                    report = undefined;
                });

                test('aggregate has correct cyclomatic complexity', function () {
                    assert.strictEqual(report.aggregate.complexity.cyclomatic, 1);
                });

                test('functions has correct length', function () {
                    assert.lengthOf(report.functions, 1);
                });

                test('function has correct name', function () {
                    assert.strictEqual(report.functions[0].name, 'foo');
                });

                test('function has complexity property', function () {
                    assert.isObject(report.functions[0].complexity);
                });

                test('function has correct cyclomatic complexity', function () {
                    assert.strictEqual(report.functions[0].complexity.cyclomatic, 1);
                });
            });

            suite('run against nested function declaration:', function () {
                var report;

                setup(function () {
                    report = cr.run('function foo () { bar(); function bar () { "baz"; } }');
                });

                teardown(function () {
                    report = undefined;
                });

                test('functions has correct length', function () {
                    assert.lengthOf(report.functions, 2);
                });

                test('first function has correct name', function () {
                    assert.strictEqual(report.functions[0].name, 'foo');
                });

                test('second function has correct name', function () {
                    assert.strictEqual(report.functions[1].name, 'bar');
                });
            });

            suite('run against function declaration containing conditional:', function () {
                var report;

                setup(function () {
                    report = cr.run('function foo () { if (true) { "bar"; } }');
                });

                teardown(function () {
                    report = undefined;
                });

                test('aggregate has correct cyclomatic complexity', function () {
                    assert.strictEqual(report.aggregate.complexity.cyclomatic, 2);
                });

                test('function has correct cyclomatic complexity', function () {
                    assert.strictEqual(report.functions[0].complexity.cyclomatic, 2);
                });
            });

            suite('run against assignment expression', function () {
                var report;

                setup(function () {
                    report = cr.run('var foo = "bar";');
                });

                teardown(function () {
                    report = undefined;
                });

                test('aggregate has correct cyclomatic complexity', function () {
                    assert.strictEqual(report.aggregate.complexity.cyclomatic, 1);
                });

                test('functions is empty', function () {
                    assert.lengthOf(report.functions, 0);
                });
            });

            suite('run against ternary condtional expression assigned to variable', function () {
                var report;

                setup(function () {
                    report = cr.run('var foo = true ? "bar" : "baz";');
                });

                teardown(function () {
                    report = undefined;
                });

                test('aggregate has correct cyclomatic complexity', function () {
                    assert.strictEqual(report.aggregate.complexity.cyclomatic, 2);
                });
            });

            suite('run against logical or expression assigned to variable', function () {
                var report;

                setup(function () {
                    report = cr.run('var foo = true || false;');
                });

                teardown(function () {
                    report = undefined;
                });

                test('aggregate has correct cyclomatic complexity', function () {
                    assert.strictEqual(report.aggregate.complexity.cyclomatic, 2);
                });
            });

            suite('run against anonymous function assigned to variable:', function () {
                var report;

                setup(function () {
                    report = cr.run('var foo = function () { "bar"; }');
                });

                teardown(function () {
                    report = undefined;
                });

                test('functions has correct length', function () {
                    assert.lengthOf(report.functions, 1);
                });

                test('function has correct name', function () {
                    assert.strictEqual(report.functions[0].name, 'foo');
                });
            });

            suite('run against named function assigned to variable:', function () {
                var report;

                setup(function () {
                    report = cr.run('var foo = function bar () { "baz"; }');
                });

                teardown(function () {
                    report = undefined;
                });

                test('function has correct name', function () {
                    assert.strictEqual(report.functions[0].name, 'bar');
                });
            });

            suite('run against ternary condtional expression returned from function', function () {
                var report;

                setup(function () {
                    report = cr.run('function foo () { return true ? "bar" : "baz"; }');
                });

                teardown(function () {
                    report = undefined;
                });

                test('aggregate has correct cyclomatic complexity', function () {
                    assert.strictEqual(report.aggregate.complexity.cyclomatic, 2);
                });

                test('function has correct cyclomatic complexity', function () {
                    assert.strictEqual(report.functions[0].complexity.cyclomatic, 2);
                });
            });

            suite('run against logical or expression returned from function', function () {
                var report;

                setup(function () {
                    report = cr.run('function foo () { return true || false; }');
                });

                teardown(function () {
                    report = undefined;
                });

                test('aggregate has correct cyclomatic complexity', function () {
                    assert.strictEqual(report.aggregate.complexity.cyclomatic, 2);
                });

                test('function has correct cyclomatic complexity', function () {
                    assert.strictEqual(report.functions[0].complexity.cyclomatic, 2);
                });
            });

            suite('run against anonymous function returned from function:', function () {
                var report;

                setup(function () {
                    report = cr.run('function foo () { return function () { "bar"; }; }');
                });

                teardown(function () {
                    report = undefined;
                });

                test('functions has correct length', function () {
                    assert.lengthOf(report.functions, 2);
                });

                test('first function has correct name', function () {
                    assert.strictEqual(report.functions[0].name, 'foo');
                });

                test('second function is anonymous', function () {
                    assert.isUndefined(report.functions[1].name);
                });
            });

            suite('run against named function returned from function:', function () {
                var report;

                setup(function () {
                    report = cr.run('function foo () { return function bar () { "baz"; }; }');
                });

                teardown(function () {
                    report = undefined;
                });

                test('second function has correct name', function () {
                    assert.strictEqual(report.functions[1].name, 'bar');
                });
            });

            suite('run against ternary condtional expression passed as argument', function () {
                var report;

                setup(function () {
                    report = cr.run('parseInt("10", true ? 10 : 8);');
                });

                teardown(function () {
                    report = undefined;
                });

                test('aggregate has correct cyclomatic complexity', function () {
                    assert.strictEqual(report.aggregate.complexity.cyclomatic, 2);
                });
            });

            suite('run against logical or expression passed as argument', function () {
                var report;

                setup(function () {
                    report = cr.run('parseInt("10", 10 || 8);');
                });

                teardown(function () {
                    report = undefined;
                });

                test('aggregate has correct cyclomatic complexity', function () {
                    assert.strictEqual(report.aggregate.complexity.cyclomatic, 2);
                });
            });

            suite('run against anonymous function passed as argument:', function () {
                var report;

                setup(function () {
                    report = cr.run('setTimeout(function () { "foo"; }, 1000);');
                });

                teardown(function () {
                    report = undefined;
                });

                test('functions has correct length', function () {
                    assert.lengthOf(report.functions, 1);
                });

                test('function is anonymous', function () {
                    assert.isUndefined(report.functions[0].name);
                });
            });

            suite('run against named function passed as argument:', function () {
                var report;

                setup(function () {
                    report = cr.run('setTimeout(function foo () { "bar"; }, 1000);');
                });

                teardown(function () {
                    report = undefined;
                });

                test('function has correct name', function () {
                    assert.strictEqual(report.functions[0].name, 'foo');
                });
            });
        });
    });
}());

<|endoftext|>


<commit_before>/*global Framework7: false, Dom7: false, Template7:false, Promise:false*/
(function () {
  'use strict';

  function transformPlayerUri(uri) {
    uri = uri.trim();
    // youtube links
    var match = /^https?:\/\/(?:www\.)?youtu(?:\.be|be\.com)\/(?:\S+\/)?(?:[^\s\/]*(?:\?|&)vi?=)?([^#?&]+)/i.exec(uri),
      newUri;
    if (match) {
      newUri = 'plugin://plugin.video.youtube/?path=/root&search&action=play_video&videoid=' + match[1];
      window.console.info('URI ' + uri + ' transformed to ' + newUri);
      return newUri;
    }
    return uri;
  }

  var ktalkApp = new Framework7(),
    $$ = Dom7,
    ktalkMessages = ktalkApp.messages('.messages', {
      autoLayout: true
    }),
    ktalkMessagebar = ktalkApp.messagebar('.messagebar'),
    ktalkAvaRecv = 'img/apple-touch-icon-114x114.png',
    ktalkAvaSent = 'img/i-form-name-ios-114x114.png',
    ktalkCommands = [{
      name: 'help',
      description: 'get the list of commands I understand.',
      regex: /^(help)/i,
      format: function () {
        var result = 'I understand the following commmands:';
        ktalkCommands.forEach(function (c) {
          if (typeof c.description !== 'undefined') {
            result += '\n\n‣ ' + c.name + ' — ' + c.description;
          }
        });
        return result;
      }
    }, {
      name: 'play <url>',
      description: 'start playing the given URL. For example,\n"play http://www.sample-videos.com/video/mp4/720/big_buck_bunny_720p_50mb.mp4",\n"play https://youtu.be/YE7VzlLtp-4",\nor simply "https://youtu.be/YE7VzlLtp-4".',
      regex: /^(?:play)?\s*((?:https?|plugin):\/\/.+)/i,
      method: 'Player.Open',
      params: function (m, c) {
        return '{"item":{"file":"' + transformPlayerUri(m.replace(c.regex, '$1')) + '"}}';
      }
    }, {
      name: 'ping',
      description: 'check the availability of the Kodi web server.',
      regex: /^(ping)/i,
      method: 'JSONRPC.Ping',
      params: '{}'
    }, {
      name: 'version',
      description: 'get the Kodi version.',
      regex: /^(version)/i,
      method: 'Application.GetProperties',
      params: '{"properties":["name","version"]}',
      format: function (m) {
        return m.name + ' ' + m.version.major + '.' + m.version.minor + (m.version.tag === 'releasecandidate' ? ' RC ' + m.version.tagversion : '') + ' (rev. ' + m.version.revision + ')';
      }
    }, {
      name: 'home',
      description: 'show the home screen.',
      regex: /^(home)/i,
      method: 'GUI.ActivateWindow',
      params: '{"window":"home"}'
    }, {
      name: 'weather',
      description: 'show the weather screen.',
      regex: /^(weather)/i,
      method: 'GUI.ActivateWindow',
      params: '{"window":"weather"}'
    }, {
      name: 'tv',
      description: 'get the list of TV channels. You can add a string to filter the channels by name, for example, "tv discovery". For sorting the list by number, use "tv#" command.',
      regex: /^(tv#?)(?:$|\s+(.*)$)/i,
      method: 'PVR.GetChannels',
      params: '{"channelgroupid":"alltv"}',
      format: function (m, c) {
        var filter = c.message.replace(c.regex, '$2').toLowerCase(),
          sortById = (c.message.replace(c.regex, '$1').indexOf('#') >= 0),
          result = '';
        m.channels.sort(function (a, b) {
          if (sortById) {
            return a.channelid - b.channelid;
          }
          return a.label.localeCompare(b.label);
        });
        m.channels.forEach(function (ch) {
          if (ch.label.toLowerCase().indexOf(filter) >= 0) {
            result += ch.channelid + ': ' + ch.label + '\n';
          }
        });
        return result;
      }
    }, {
      name: 'exec <method> <params>',
      description: 'for geeks only: execute the JSON-RPC <method> with <params>. For example,\n"exec GUI.ActivateWindow {"window":"home"}".',
      regex: /^exec\s+([\w\.]+)\s+(\S+)/i,
      method: '$1',
      params: '$2'
    }, {
      name: 'debug <js object>',
      regex: /^debug\s+(.+)/i,
      format: function (m, c) {
        var val = c.message.replace(c.regex, '$1');
        return '# ' + val + ' =\n' + JSON.stringify(eval(val), null, 2);
      }
    }],
    lastMessageTime = 0;

  function q(v) {
    return Promise.resolve(v);
  }

  function r(v) {
    return Promise.reject(v);
  }

  function formatDay(d) {
    var date = d ? new Date(d) : new Date(),
      weekDay = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'][date.getDay()],
      day = date.getDate(),
      month = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'][date.getMonth()];
    return weekDay + ', ' + month + ' ' + day;
  }

  function formatTime(d) {
    var date = d ? new Date(d) : new Date(),
      hours = date.getHours(),
      mins = date.getMinutes();
    return (hours < 10 ? '0' : '') + hours + (mins < 10 ? ':0' : ':') + mins;
  }

  function formatDate(d) {
    return formatDay(d) + ', ' + formatTime(d) + '';
  }

  function formatJson(o) {
    return (JSON.stringify(o, null, 4) + '\n')
      .replace(/^ *[\{\}] *(?:\r\n|\r|\n)+/gm, '')
      .replace(/ *[\{\}] *|"/g, '')
      .replace(/ *, *$/gm, '')
      .replace(/\[/g, '(')
      .replace(/\]/g, ')')
      .trimRight();
  }

  function encodeHtmlEntities(s) {
    var SURROGATE_PAIR_REGEXP = /[\uD800-\uDBFF][\uDC00-\uDFFF]/g,
      NON_ALPHANUMERIC_REGEXP = /([^\#-~ |!])/g;

    return s.replace(/&/g, '&')
      .replace(SURROGATE_PAIR_REGEXP, function (s) {
        var hi = s.charCodeAt(0),
          low = s.charCodeAt(1);
        return '&#' + (((hi - 0xD800) * 0x400) + (low - 0xDC00) + 0x10000) + ';';
      })
      .replace(NON_ALPHANUMERIC_REGEXP, function (s) {
        return '&#' + s.charCodeAt(0) + ';';
      })
      .replace(/</g, '<')
      .replace(/>/g, '>');
  }

  function makeMessageProps(text, type) {
    var props = {
        type: type,
        text: encodeHtmlEntities(text)
      },
      date = new Date();
    if (date.getTime() - lastMessageTime > 10 * 60 * 1000) {
      props.day = formatDay(date);
      props.time = formatTime(date);
    }
    props.avatar = props.type === 'sent' ? ktalkAvaSent : ktalkAvaRecv;
    lastMessageTime = date.getTime();
    return props;
  }

  function fixMessageTemplate() {
    // fix space between {{day}} and ","
    if (ktalkMessages.params.messageTemplate.indexOf('{{day}} {{#if time}},') >= 0) {
      ktalkMessages.params.messageTemplate = ktalkMessages.params.messageTemplate.replace('{{day}} {{#if time}},', '{{day}}{{#if time}},');
      ktalkMessages.template = Template7.compile(ktalkMessages.params.messageTemplate);
      window.console.info('Message template fixed');
    }
  }

  function talkToKodi(message) {
    var command; // current command

    function checkMessage(m) {
      m = m.trim();
      if (m.length > 0) {
        return q(m);
      } else {
        return r();
      }
    }

    function addQuestionMessage(m) {
      ktalkMessages.addMessage(makeMessageProps(m, 'sent'));
      return m;
    }

    function parseProperty(message, command, propName, toJson) {
      var result;
      if (typeof command[propName] === 'undefined') {
        return;
      }
      if (typeof command[propName] === 'function') {
        result = command[propName](message, command);
        if (typeof result === 'object' && !toJson) {
          return JSON.stringify(result);
        }
        if (typeof result !== 'object' && toJson) {
          return JSON.parse(result);
        }
        return result;
      }
      if (typeof command[propName] === 'object') {
        command[propName] = JSON.stringify(command[propName]);
      } else if (typeof command[propName] !== 'string') {
        command[propName] = command[propName].toString();
      }
      if (command[propName].indexOf('$') >= 0) { // regex replace
        result = message.replace(command.regex, command[propName]);
      } else {
        result = command[propName];
      }
      return toJson ? JSON.parse(result) : result;
    }

    function parseKodiCommand(message) {
      var request;
      ktalkCommands.some(function (c) {
        if (c.regex.test(message)) {
          request = {
            method: parseProperty(message, c, 'method'),
            params: parseProperty(message, c, 'params', true)
          };
          command = {
            message: message,
            name: c.name,
            description: c.description,
            regex: c.regex,
            method: request.method,
            params: request.params,
            format: c.format
          };
          return true;
        }
        return false;
      });
      if (typeof request !== 'undefined') {
        return request;
      }
      return r('Sorry, I can\'t understand you. I will learn more commands soon.');
    }

    function formatAnswerMessage(m) {
      if (typeof command.format !== 'undefined') {
        return parseProperty(m, command, 'format');
      }
      if (typeof m === 'string') {
        return m + '!';
      }
      return 'OK, the answer is:\n\n' + formatJson(m);
    }

    function formatErrorMessage(m) {
      if (typeof m === 'undefined') {
        return '';
      } else if (typeof m === 'object') {
        var result = 'ERROR';
        result += m.code ? ' ' + m.code + ':' : ':';
        result += m.name ? ' [' + m.name + ']' : '';
        result += ' ' + m.message;
        return result;
      }
      return m.toString().trim();
    }

    function addAnswerMessage(m) {
      m = formatAnswerMessage(m);
      ktalkMessages.addMessage(makeMessageProps(m, 'received'));
      return m;
    }

    function addErrorMessage(m) {
      m = formatErrorMessage(m);
      if (m.length > 0) {
        ktalkMessages.addMessage(makeMessageProps(m, 'received')).classList.add(m.indexOf('#') === 0 ? 'debug' : 'error');
      }
      return m;
    }

    return checkMessage(message)
      .then(addQuestionMessage)
      .then(parseKodiCommand)
      .then(window.kodi.call)
      .then(addAnswerMessage, addErrorMessage);
  }

  function addInfoMessages(msg) {
    if (typeof msg === 'string') {
      msg = [msg];
    }
    msg.forEach(function (m) {
      ktalkMessages.addMessage(makeMessageProps(m, 'received'));
    });
  }

  function addGreeting() {
    addInfoMessages(['Hello, I\'m a Kodi Talk bot.', 'You can send me an URI to play or another command (try to type "help" for the list of commands I understand)']);
  }

  function addSampleKodiTalk() {
    var st = [
      'help',
      'ping',
      'version',
      'hello'
    ];
    // Send messages in a sequential manner
    st.reduce(function (p, c) {
      return p.then(function () {
        return talkToKodi(c);
      });
    }, q());
  }

  function sendMessage() {
    talkToKodi(ktalkMessagebar.value());
    ktalkMessagebar.clear();
  }

  // Fix for missing iPhone status bar in landscape mode
  ktalkApp.device.statusBar = false;
  $$('html').removeClass('with-statusbar-overlay');

  // Handle message
  $$('.messagebar .link').on('click', function () {
    sendMessage();
  });

  $$('.messagebar textarea').on('keypress', function (e) {
    e = e || window.event;
    if ((e.which || e.keyCode) === 13) {
      e.preventDefault();
      sendMessage();
    }
  });

  fixMessageTemplate();
  addGreeting();
  // addInfoMessages([formatJson(ktalkApp.device)]);
  // addSampleKodiTalk();
  if (!ktalkApp.device.os) {
    setTimeout(function () {
      $$('.messagebar textarea').focus();
    }, 100);
  }
}());
<commit_msg>addAnswerMessage() and addErrorMessage() functions promisification.<commit_after>/*global Framework7: false, Dom7: false, Template7:false, Promise:false*/
(function () {
  'use strict';

  function transformPlayerUri(uri) {
    uri = uri.trim();
    // youtube links
    var match = /^https?:\/\/(?:www\.)?youtu(?:\.be|be\.com)\/(?:\S+\/)?(?:[^\s\/]*(?:\?|&)vi?=)?([^#?&]+)/i.exec(uri),
      newUri;
    if (match) {
      newUri = 'plugin://plugin.video.youtube/?path=/root&search&action=play_video&videoid=' + match[1];
      window.console.info('URI ' + uri + ' transformed to ' + newUri);
      return newUri;
    }
    return uri;
  }

  var ktalkApp = new Framework7(),
    $$ = Dom7,
    ktalkMessages = ktalkApp.messages('.messages', {
      autoLayout: true
    }),
    ktalkMessagebar = ktalkApp.messagebar('.messagebar'),
    ktalkAvaRecv = 'img/apple-touch-icon-114x114.png',
    ktalkAvaSent = 'img/i-form-name-ios-114x114.png',
    ktalkCommands = [{
      name: 'help',
      description: 'get the list of commands I understand.',
      regex: /^(help)/i,
      format: function () {
        var result = 'I understand the following commmands:';
        ktalkCommands.forEach(function (c) {
          if (typeof c.description !== 'undefined') {
            result += '\n\n‣ ' + c.name + ' — ' + c.description;
          }
        });
        return result;
      }
    }, {
      name: 'play <url>',
      description: 'start playing the given URL. For example,\n"play http://www.sample-videos.com/video/mp4/720/big_buck_bunny_720p_50mb.mp4",\n"play https://youtu.be/YE7VzlLtp-4",\nor simply "https://youtu.be/YE7VzlLtp-4".',
      regex: /^(?:play)?\s*((?:https?|plugin):\/\/.+)/i,
      method: 'Player.Open',
      params: function (m, c) {
        return '{"item":{"file":"' + transformPlayerUri(m.replace(c.regex, '$1')) + '"}}';
      }
    }, {
      name: 'ping',
      description: 'check the availability of the Kodi web server.',
      regex: /^(ping)/i,
      method: 'JSONRPC.Ping',
      params: '{}'
    }, {
      name: 'version',
      description: 'get the Kodi version.',
      regex: /^(version)/i,
      method: 'Application.GetProperties',
      params: '{"properties":["name","version"]}',
      format: function (m) {
        return m.name + ' ' + m.version.major + '.' + m.version.minor + (m.version.tag === 'releasecandidate' ? ' RC ' + m.version.tagversion : '') + ' (rev. ' + m.version.revision + ')';
      }
    }, {
      name: 'home',
      description: 'show the home screen.',
      regex: /^(home)/i,
      method: 'GUI.ActivateWindow',
      params: '{"window":"home"}'
    }, {
      name: 'weather',
      description: 'show the weather screen.',
      regex: /^(weather)/i,
      method: 'GUI.ActivateWindow',
      params: '{"window":"weather"}'
    }, {
      name: 'tv',
      description: 'get the list of TV channels. You can add a string to filter the channels by name, for example, "tv discovery". For sorting the list by number, use "tv#" command.',
      regex: /^(tv#?)(?:$|\s+(.*)$)/i,
      method: 'PVR.GetChannels',
      params: '{"channelgroupid":"alltv"}',
      format: function (m, c) {
        var filter = c.message.replace(c.regex, '$2').toLowerCase(),
          sortById = (c.message.replace(c.regex, '$1').indexOf('#') >= 0),
          result = '';
        m.channels.sort(function (a, b) {
          if (sortById) {
            return a.channelid - b.channelid;
          }
          return a.label.localeCompare(b.label);
        });
        m.channels.forEach(function (ch) {
          if (ch.label.toLowerCase().indexOf(filter) >= 0) {
            result += ch.channelid + ': ' + ch.label + '\n';
          }
        });
        return result;
      }
    }, {
      name: 'exec <method> <params>',
      description: 'for geeks only: execute the JSON-RPC <method> with <params>. For example,\n"exec GUI.ActivateWindow {"window":"home"}".',
      regex: /^exec\s+([\w\.]+)\s+(\S+)/i,
      method: '$1',
      params: '$2'
    }, {
      name: 'debug <js object>',
      regex: /^debug\s+(.+)/i,
      format: function (m, c) {
        var val = c.message.replace(c.regex, '$1');
        return '# ' + val + ' =\n' + JSON.stringify(eval(val), null, 2);
      }
    }],
    lastMessageTime = 0;

  function q(v) {
    return Promise.resolve(v);
  }

  function r(v) {
    return Promise.reject(v);
  }

  function formatDay(d) {
    var date = d ? new Date(d) : new Date(),
      weekDay = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'][date.getDay()],
      day = date.getDate(),
      month = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'][date.getMonth()];
    return weekDay + ', ' + month + ' ' + day;
  }

  function formatTime(d) {
    var date = d ? new Date(d) : new Date(),
      hours = date.getHours(),
      mins = date.getMinutes();
    return (hours < 10 ? '0' : '') + hours + (mins < 10 ? ':0' : ':') + mins;
  }

  function formatDate(d) {
    return formatDay(d) + ', ' + formatTime(d) + '';
  }

  function formatJson(o) {
    return (JSON.stringify(o, null, 4) + '\n')
      .replace(/^ *[\{\}] *(?:\r\n|\r|\n)+/gm, '')
      .replace(/ *[\{\}] *|"/g, '')
      .replace(/ *, *$/gm, '')
      .replace(/\[/g, '(')
      .replace(/\]/g, ')')
      .trimRight();
  }

  function encodeHtmlEntities(s) {
    var SURROGATE_PAIR_REGEXP = /[\uD800-\uDBFF][\uDC00-\uDFFF]/g,
      NON_ALPHANUMERIC_REGEXP = /([^\#-~ |!])/g;

    return s.replace(/&/g, '&')
      .replace(SURROGATE_PAIR_REGEXP, function (s) {
        var hi = s.charCodeAt(0),
          low = s.charCodeAt(1);
        return '&#' + (((hi - 0xD800) * 0x400) + (low - 0xDC00) + 0x10000) + ';';
      })
      .replace(NON_ALPHANUMERIC_REGEXP, function (s) {
        return '&#' + s.charCodeAt(0) + ';';
      })
      .replace(/</g, '<')
      .replace(/>/g, '>');
  }

  function makeMessageProps(text, type) {
    var props = {
        type: type,
        text: encodeHtmlEntities(text)
      },
      date = new Date();
    if (date.getTime() - lastMessageTime > 10 * 60 * 1000) {
      props.day = formatDay(date);
      props.time = formatTime(date);
    }
    props.avatar = props.type === 'sent' ? ktalkAvaSent : ktalkAvaRecv;
    lastMessageTime = date.getTime();
    return props;
  }

  function fixMessageTemplate() {
    // fix space between {{day}} and ","
    if (ktalkMessages.params.messageTemplate.indexOf('{{day}} {{#if time}},') >= 0) {
      ktalkMessages.params.messageTemplate = ktalkMessages.params.messageTemplate.replace('{{day}} {{#if time}},', '{{day}}{{#if time}},');
      ktalkMessages.template = Template7.compile(ktalkMessages.params.messageTemplate);
      window.console.info('Message template fixed');
    }
  }

  function talkToKodi(message) {
    var command; // current command

    function checkMessage(m) {
      m = m.trim();
      if (m.length > 0) {
        return q(m);
      } else {
        return r();
      }
    }

    function addQuestionMessage(m) {
      ktalkMessages.addMessage(makeMessageProps(m, 'sent'));
      return m;
    }

    function parseProperty(message, command, propName, toJson) {
      var result;
      if (typeof command[propName] === 'undefined') {
        return;
      }
      if (typeof command[propName] === 'function') {
        result = command[propName](message, command);
        if (typeof result === 'object' && !toJson) {
          return JSON.stringify(result);
        }
        if (typeof result !== 'object' && toJson) {
          return JSON.parse(result);
        }
        return result;
      }
      if (typeof command[propName] === 'object') {
        command[propName] = JSON.stringify(command[propName]);
      } else if (typeof command[propName] !== 'string') {
        command[propName] = command[propName].toString();
      }
      if (command[propName].indexOf('$') >= 0) { // regex replace
        result = message.replace(command.regex, command[propName]);
      } else {
        result = command[propName];
      }
      return toJson ? JSON.parse(result) : result;
    }

    function parseKodiCommand(message) {
      var request;
      ktalkCommands.some(function (c) {
        if (c.regex.test(message)) {
          request = {
            method: parseProperty(message, c, 'method'),
            params: parseProperty(message, c, 'params', true)
          };
          command = {
            message: message,
            name: c.name,
            description: c.description,
            regex: c.regex,
            method: request.method,
            params: request.params,
            format: c.format
          };
          return true;
        }
        return false;
      });
      if (typeof request !== 'undefined') {
        return request;
      }
      return r('Sorry, I can\'t understand you. I will learn more commands soon.');
    }

    function formatAnswerMessage(m) {
      if (typeof command.format !== 'undefined') {
        return parseProperty(m, command, 'format');
      }
      if (typeof m === 'string') {
        return m + '!';
      }
      return 'OK, the answer is:\n\n' + formatJson(m);
    }

    function formatErrorMessage(m) {
      if (typeof m === 'undefined') {
        return '';
      } else if (typeof m === 'object') {
        var result = 'ERROR';
        result += m.code ? ' ' + m.code + ':' : ':';
        result += m.name ? ' [' + m.name + ']' : '';
        result += ' ' + m.message;
        return result;
      }
      return m.toString().trim();
    }

    function addReceivedMessage(message, format, className) {
      return q(message).then(format).then(function (message) {
        if (message.length === 0) {
          return null;
        }
        var elm = ktalkMessages.addMessage(makeMessageProps(message, 'received'));
        if (message.indexOf('#') === 0) {
          elm.classList.add('debug');
        } else if (className) {
          elm.classList.add(className);
        }
        return elm;
      });
    }

    function addAnswerMessage(message) {
      return addReceivedMessage(message, formatAnswerMessage);
    }

    function addErrorMessage(message) {
      return addReceivedMessage(message, formatErrorMessage, 'error');
    }

    return checkMessage(message)
      .then(addQuestionMessage)
      .then(parseKodiCommand)
      .then(window.kodi.call)
      .then(addAnswerMessage, addErrorMessage);
  }

  function addInfoMessages(msg) {
    if (typeof msg === 'string') {
      msg = [msg];
    }
    msg.forEach(function (m) {
      ktalkMessages.addMessage(makeMessageProps(m, 'received'));
    });
  }

  function addGreeting() {
    addInfoMessages(['Hello, I\'m a Kodi Talk bot.', 'You can send me an URI to play or another command (try to type "help" for the list of commands I understand)']);
  }

  function addSampleKodiTalk() {
    var st = [
      'help',
      'ping',
      'version',
      'hello'
    ];
    // Send messages in a sequential manner
    st.reduce(function (p, c) {
      return p.then(function () {
        return talkToKodi(c);
      });
    }, q());
  }

  function sendMessage() {
    talkToKodi(ktalkMessagebar.value());
    ktalkMessagebar.clear();
  }

  // Fix for missing iPhone status bar in landscape mode
  ktalkApp.device.statusBar = false;
  $$('html').removeClass('with-statusbar-overlay');

  // Handle message
  $$('.messagebar .link').on('click', function () {
    sendMessage();
  });

  $$('.messagebar textarea').on('keypress', function (e) {
    e = e || window.event;
    if ((e.which || e.keyCode) === 13) {
      e.preventDefault();
      sendMessage();
    }
  });

  fixMessageTemplate();
  addGreeting();
  // addInfoMessages([formatJson(ktalkApp.device)]);
  // addSampleKodiTalk();
  if (!ktalkApp.device.os) {
    setTimeout(function () {
      $$('.messagebar textarea').focus();
    }, 100);
  }
}());
<|endoftext|>