<commit_before>const http = require('http');
const events = require('events');
const serverPort = 36001;
const librato = require('../lib/librato.js');

let emitter;

module.exports = {
  setUp: function(callback) {
    emitter = new events.EventEmitter();
    this.server = http.createServer();
    this.server.listen(serverPort, '127.0.0.1', function() {
      callback();
    });

    this.api_mock = function(validRequest, errorResponse, callback) {
      return function(req, res) {
        let data = '';
        req.on('data', function(chunk) {
          data += chunk;
        });
        req.on('end', function() {
          let body = JSON.parse(data);
          if (validRequest) {
            res.writeHead(200, {});
            res.end('');
            callback(req, res, body);
          } else {
            res.writeHead(400, {'content-type': 'application/json'});
            res.end(JSON.stringify(errorResponse));
            callback(req, res, body);
          }
        });
      };
    };
    // Librato Backend
    librato.init(null, {
      debug: false,
      librato: {
        email: '-@-',
        token: '-',
        api: 'http://127.0.0.1:' + serverPort,
        writeToLegacy: false,
        batchSize: 5,
      },
    }, emitter);
  },
  tearDown: function(callback) {
    this.server.close(function() {
      callback();
    });
  },
  testValidMeasurementNoTags: function(test) {
    let metrics = {gauges: {my_gauge: 1}};

    this.server.once('request', this.api_mock(true, {}, function(req, res, body) {
      let measurement = body.measurements[0];
      test.ok(measurement);
      test.equal(measurement.name, 'my_gauge');
      test.equal(measurement.value, 1);
      test.deepEqual(measurement.tags, {});
      test.done();
    }));
    emitter.emit('flush', 123, metrics);
  },

  testValidMeasurementSingleTag: function(test) {
    test.expect(4);
    let metrics = {gauges: {'my_gauge#foo=bar': 1}};
    this.server.once('request', this.api_mock(true, {}, function(req, res, body) {
      let measurement = body.measurements[0];
      test.ok(measurement);
      test.equal(measurement.name, 'my_gauge');
      test.equal(measurement.value, 1);
      test.deepEqual(measurement.tags, {foo: 'bar'});
      test.done();
    }));
    emitter.emit('flush', 123, metrics);
  },
  testValidMeasurementMultipleTags: function(test) {
    test.expect(4);
    let metrics = {gauges: {'my_gauge#foo=bar,biz=baz': 1}};
    this.server.once('request', this.api_mock(true, {}, function(req, res, body) {
      let measurement = body.measurements[0];
      test.ok(measurement);
      test.equal(measurement.name, 'my_gauge');
      test.equal(measurement.value, 1);
      test.deepEqual(measurement.tags, {
        foo: 'bar',
        biz: 'baz',
      });
      test.done();
    }));
    emitter.emit('flush', 123, metrics);
  },
  testIgnoreBrokenMetrics: function(test) {
    test.expect(5);
    let metrics = {
      gauges: {
        cool_gauge: 123,
        bad_counter: 321,
      },
    };
    let errors = {errors: {params: {type: ['\'bad_counter\'' + ' is a counter, but was' + ' submitted as different type']}}};
    // Simulate failure...
    this.server.once('request', this.api_mock(false, errors, function(req, res, body) {
      let gauges = {};
      for (let k in body.measurements) {
        gauges[body.measurements[k].name] = body.measurements[k].value;
      }
      test.ok(gauges.cool_gauge);
      test.ok(gauges.bad_counter);
      test.equal(res.statusCode, 400);
      setTimeout(function() {
        this.server.once('request', this.api_mock(true, {}, function(req, res, body) {
          let gauges = {};
          for (let k in body.measurements) {
            gauges[body.measurements[k].name] = body.measurements[k].value;
          }
          test.ok(gauges.cool_gauge);
          test.strictEqual(gauges.bad_counter, undefined);
          test.done();
        }));
        emitter.emit('flush', 123, metrics);
      }.bind(this), 500);
    }.bind(this)));
    emitter.emit('flush', 123, metrics);
  },
  testTimers: function(test) {
    test.expect(7);
    let metrics = {
      timers: {
        'my_timer#tag=foo': [
          41,
          73.5,
        ],
      },
      timer_data: {'my_timer#tag=foo': null},
    };
    this.server.once('request', this.api_mock(true, {}, function(req, res, body) {
      let measurement = body.measurements[0];
      test.ok(measurement);
      test.equal(measurement.name, 'my_timer');
      test.equal(measurement.value, undefined);
      test.equal(measurement.min, 41);
      test.equal(measurement.max, 73.5);
      test.equal(measurement.sum, 114.5);
      test.deepEqual(measurement.tags, {tag: 'foo'});
      test.done();
    }));
    emitter.emit('flush', 123, metrics);
  },

  testMaxBatchSize: function(test) {
    test.expect(0);
    var gauges = {};
    for (var i = 0; i < 5; i++) {
      var key = 'gauge' + i;
      gauges[key] = 1;
    }
    var metrics = {gauges: gauges};

    this.server.once('request', this.api_mock(true, {}, function(req, res, body) {
    }));

    this.emitter.emit('flush', 123, metrics);
    test.done();
  },
};
<commit_msg>Strict mode<commit_after>'use strict';

const http = require('http');
const events = require('events');
const serverPort = 36001;
const librato = require('../lib/librato.js');

let emitter;

module.exports = {
  setUp: function(callback) {
    emitter = new events.EventEmitter();
    this.server = http.createServer();
    this.server.listen(serverPort, '127.0.0.1', function() {
      callback();
    });

    this.api_mock = function(validRequest, errorResponse, callback) {
      return function(req, res) {
        let data = '';
        req.on('data', function(chunk) {
          data += chunk;
        });
        req.on('end', function() {
          let body = JSON.parse(data);
          if (validRequest) {
            res.writeHead(200, {});
            res.end('');
            callback(req, res, body);
          } else {
            res.writeHead(400, {'content-type': 'application/json'});
            res.end(JSON.stringify(errorResponse));
            callback(req, res, body);
          }
        });
      };
    };
    // Librato Backend
    librato.init(null, {
      debug: false,
      librato: {
        email: '-@-',
        token: '-',
        api: 'http://127.0.0.1:' + serverPort,
        writeToLegacy: false,
        batchSize: 5,
      },
    }, emitter);
  },
  tearDown: function(callback) {
    this.server.close(function() {
      callback();
    });
  },
  testValidMeasurementNoTags: function(test) {
    let metrics = {gauges: {my_gauge: 1}};

    this.server.once('request', this.api_mock(true, {}, function(req, res, body) {
      let measurement = body.measurements[0];
      test.ok(measurement);
      test.equal(measurement.name, 'my_gauge');
      test.equal(measurement.value, 1);
      test.deepEqual(measurement.tags, {});
      test.done();
    }));
    emitter.emit('flush', 123, metrics);
  },

  testValidMeasurementSingleTag: function(test) {
    test.expect(4);
    let metrics = {gauges: {'my_gauge#foo=bar': 1}};
    this.server.once('request', this.api_mock(true, {}, function(req, res, body) {
      let measurement = body.measurements[0];
      test.ok(measurement);
      test.equal(measurement.name, 'my_gauge');
      test.equal(measurement.value, 1);
      test.deepEqual(measurement.tags, {foo: 'bar'});
      test.done();
    }));
    emitter.emit('flush', 123, metrics);
  },
  testValidMeasurementMultipleTags: function(test) {
    test.expect(4);
    let metrics = {gauges: {'my_gauge#foo=bar,biz=baz': 1}};
    this.server.once('request', this.api_mock(true, {}, function(req, res, body) {
      let measurement = body.measurements[0];
      test.ok(measurement);
      test.equal(measurement.name, 'my_gauge');
      test.equal(measurement.value, 1);
      test.deepEqual(measurement.tags, {
        foo: 'bar',
        biz: 'baz',
      });
      test.done();
    }));
    emitter.emit('flush', 123, metrics);
  },
  testIgnoreBrokenMetrics: function(test) {
    test.expect(5);
    let metrics = {
      gauges: {
        cool_gauge: 123,
        bad_counter: 321,
      },
    };
    let errors = {errors: {params: {type: ['\'bad_counter\'' + ' is a counter, but was' + ' submitted as different type']}}};
    // Simulate failure...
    this.server.once('request', this.api_mock(false, errors, function(req, res, body) {
      let gauges = {};
      for (let k in body.measurements) {
        gauges[body.measurements[k].name] = body.measurements[k].value;
      }
      test.ok(gauges.cool_gauge);
      test.ok(gauges.bad_counter);
      test.equal(res.statusCode, 400);
      setTimeout(function() {
        this.server.once('request', this.api_mock(true, {}, function(req, res, body) {
          let gauges = {};
          for (let k in body.measurements) {
            gauges[body.measurements[k].name] = body.measurements[k].value;
          }
          test.ok(gauges.cool_gauge);
          test.strictEqual(gauges.bad_counter, undefined);
          test.done();
        }));
        emitter.emit('flush', 123, metrics);
      }.bind(this), 500);
    }.bind(this)));
    emitter.emit('flush', 123, metrics);
  },
  testTimers: function(test) {
    test.expect(7);
    let metrics = {
      timers: {
        'my_timer#tag=foo': [
          41,
          73.5,
        ],
      },
      timer_data: {'my_timer#tag=foo': null},
    };
    this.server.once('request', this.api_mock(true, {}, function(req, res, body) {
      let measurement = body.measurements[0];
      test.ok(measurement);
      test.equal(measurement.name, 'my_timer');
      test.equal(measurement.value, undefined);
      test.equal(measurement.min, 41);
      test.equal(measurement.max, 73.5);
      test.equal(measurement.sum, 114.5);
      test.deepEqual(measurement.tags, {tag: 'foo'});
      test.done();
    }));
    emitter.emit('flush', 123, metrics);
  },

  testMaxBatchSize: function(test) {
    test.expect(0);
    var gauges = {};
    for (var i = 0; i < 5; i++) {
      var key = 'gauge' + i;
      gauges[key] = 1;
    }
    var metrics = {gauges: gauges};

    this.server.once('request', this.api_mock(true, {}, function(req, res, body) {
    }));

    this.emitter.emit('flush', 123, metrics);
    test.done();
  },
};
<|endoftext|>


<commit_before>/**
Bootstrap datefield input - modification for inline mode.
Shows normal <input type="text"> and binds popup datepicker.
Automatically shown in inline mode.

@class datefield
@extends date

@since 1.4.0
**/
(function ($) {
    "use strict";

    var DateField = function (options) {
        this.init('datefield', options, DateField.defaults);
        this.initPicker(options, DateField.defaults);
    };

    $.fn.editableutils.inherit(DateField, $.fn.editabletypes.date);

    $.extend(DateField.prototype, {
        render: function () {
            this.$input = this.$tpl.find('input');
            this.setClass();
            this.setAttr('placeholder');

            //bootstrap-datepicker is set `bdateicker` to exclude conflict with jQuery UI one. (in date.js)
            this.$tpl.bdatepicker(this.options.datepicker);

            //need to disable original event handlers
            this.$input.off('focus keydown');

            //update value of datepicker
            this.$input.keyup($.proxy(function(){
               this.$tpl.removeData('date');
               this.$tpl.bdatepicker('update');
            }, this));

        },

       value2input: function(value) {
           this.$input.val(value ? this.dpg.formatDate(value, this.parsedViewFormat, this.options.datepicker.language) : '');
           this.$tpl.bdatepicker('update');
       },

       input2value: function() {
           return this.html2value(this.$input.val());
       },

       activate: function() {
           $.fn.editabletypes.text.prototype.activate.call(this);
       },

       autosubmit: function() {
         //reset autosubmit to empty
       }
    });

    DateField.defaults = $.extend({}, $.fn.editabletypes.date.defaults, {
        /**
        @property tpl
        **/
        tpl:'
<input type="text"/>
',
        /**
        @property inputclass
        @default 'input-small'
        **/
        inputclass: 'input-small',

        /* datepicker config */
        datepicker: {
            weekStart: 0,
            startView: 0,
            minViewMode: 0,
            autoclose: true
        }
    });

    $.fn.editabletypes.datefield = DateField;

}(window.jQuery));
<commit_msg>BS4 datepicker needs to be called on the input rather than container<commit_after>/**
Bootstrap datefield input - modification for inline mode.
Shows normal <input type="text"> and binds popup datepicker.
Automatically shown in inline mode.

@class datefield
@extends date

@since 1.4.0
**/
(function ($) {
    "use strict";

    var DateField = function (options) {
        this.init('datefield', options, DateField.defaults);
        this.initPicker(options, DateField.defaults);
    };

    $.fn.editableutils.inherit(DateField, $.fn.editabletypes.date);

    $.extend(DateField.prototype, {
        render: function () {
            this.$input = this.$tpl.find('input');
            this.setClass();
            this.setAttr('placeholder');

            //bootstrap-datepicker is set `bdateicker` to exclude conflict with jQuery UI one. (in date.js)
            this.$input.bdatepicker(this.options.datepicker);

            //need to disable original event handlers
            this.$input.off('focus keydown');

            //update value of datepicker
            this.$input.keyup($.proxy(function(){
               this.$tpl.removeData('date');
               this.$tpl.bdatepicker('update');
            }, this));

        },

       value2input: function(value) {
           this.$input.val(value ? this.dpg.formatDate(value, this.parsedViewFormat, this.options.datepicker.language) : '');
           this.$tpl.bdatepicker('update');
       },

       input2value: function() {
           return this.html2value(this.$input.val());
       },

       activate: function() {
           $.fn.editabletypes.text.prototype.activate.call(this);
       },

       autosubmit: function() {
         //reset autosubmit to empty
       }
    });

    DateField.defaults = $.extend({}, $.fn.editabletypes.date.defaults, {
        /**
        @property tpl
        **/
        tpl:'
<input type="text"/>
',
        /**
        @property inputclass
        @default 'input-small'
        **/
        inputclass: 'input-small',

        /* datepicker config */
        datepicker: {
            weekStart: 0,
            startView: 0,
            minViewMode: 0,
            autoclose: true
        }
    });

    $.fn.editabletypes.datefield = DateField;

}(window.jQuery));
<|endoftext|>


<commit_before>
const fs = require('fs');
const expect = require('expect');

const nearley = require('../lib/nearley');
const {compile, evalGrammar, parse, nearleyc} = require('./_shared');
const {sh, externalNearleyc, cleanup} = require('./external');

function read(filename) {
    return fs.readFileSync(filename, 'utf-8');
}

describe("bin/nearleyc", function() {
    after(cleanup)

    it('builds for ES5', function() {
        const out = externalNearleyc("grammars/parens.ne", '.js')
        var grammar = nearley.Grammar.fromCompiled(require(`./${out}.js`));
    });

    it('builds for CoffeeScript', function() {
        const out = externalNearleyc("grammars/coffeescript-test.ne", ".coffee")
        sh(`coffee -c ${out}.coffee`);
        var grammar = nearley.Grammar.fromCompiled(require(`./${out}.js`));
        expect(parse(grammar, "ABCDEFZ12309")).toEqual([ [ 'ABCDEFZ', '12309' ] ]);
    });

    it('builds for TypeScript', function() {
        this.timeout(10000); // It takes a while to run tsc!
        const out = externalNearleyc("grammars/typescript-test.ne", ".ts")
        sh(`tsc ${out}.ts`);
        var grammar = nearley.Grammar.fromCompiled(require(`./${out}.js`));
        expect(parse(grammar, "<123>")).toEqual([ [ '<', '123', '>' ] ]);
    });

})

describe('nearleyc', function() {

    it('calculator example', function() {
        var arith = compile(read("examples/calculator/arithmetic.ne"));
        expect(parse(arith, "ln (3 + 2*(8/e - sin(pi/5)))")).toEqual([ Math.log(3 + 2*(8/Math.exp(1) - Math.sin(Math.PI/5))) ]);
    });

    it('csscolor example', function() {
        var cssc = compile(read("examples/csscolor.ne"));
        expect(parse(cssc, "#FF00FF")).toEqual([{r: 0xff, g: 0x00, b: 0xff}]);
        expect(parse(cssc, "#8A7")).toEqual([{r: 0x88, g: 0xaa, b: 0x77}]);
        expect(parse(cssc, "rgb(99,66,33)")).toEqual([{r: 99, g: 66, b: 33}]);
        expect(parse(cssc, "hsl(99,66,33)")).toEqual([{h: 99, s: 66, l: 33}]);
        expect(function() { parse(cssc, "#badcolor"); }).toThrow()
    });

    it('exponential whitespace bug', function() {
        compile(read('test/grammars/indentation.ne'));
    });

    it('nullable whitespace bug', function() {
        var wsb = compile(read("test/grammars/whitespace.ne"));
        expect(parse(wsb, "(x)")).toEqual(
            [ [ [ [ '(', null, [ [ [ [ 'x' ] ] ] ], null, ')' ] ] ] ]);
    });

    it('percent bug', function() {
        compile(read('test/grammars/percent.ne'));
    });

    it('tokens', function() {
        var tokc = compile(read("examples/token.ne"));
        expect(parse(tokc, [123, 456, " ", 789])).toEqual([ [123, [ [ 456, " ", 789 ] ]] ]);
    });

    it('leo bug', function() {
        var leo = compile(read("test/grammars/leobug.ne"));
        expect(parse(leo, "baab")).toEqual(
            [ [ 'b', [], 'a', [], 'a', [ 'b' ] ],
            [ 'b', [], 'a', [], 'a', [ 'b', [] ] ] ]);
    });

    var json;
    it('json example compiles', function() {
        json = compile(read("examples/json.ne"));
    });
    it('json test1', function() {
        var test1 = read('test/grammars/test1.json');
        expect(parse(json, test1)).toEqual([JSON.parse(test1)]);
    });
    it('json test2', function() {
        var test2 = read('test/grammars/test2.json');
        expect(parse(json, test2)).toEqual([JSON.parse(test2)]);
    });

    it('tosh example', function() {
        var tosh = compile(read("examples/tosh.ne"));
        expect(parse(tosh, "set foo to 2 * e^ of ( foo * -0.05 + 0.5) * (1 - e ^ of (foo * -0.05 + 0.5))"))
            .toEqual([["setVar:to:","foo",["*",["*",2,["computeFunction:of:","e ^",["+",["*",["readVariable","foo"],-0.05],0.5]]],["-",1,["computeFunction:of:","e ^",["+",["*",["readVariable","foo"],-0.05],0.5]]]]]]);
    });

    it('classic crontab', function() {
        // Try compiling the grammar
        var classicCrontab = compile(read("examples/classic_crontab.ne"));
        // Try parsing crontab file using the newly generated parser
        var crontabTest = read('test/grammars/classic_crontab.test');
        var crontabResults = read('test/grammars/classic_crontab.results');
        expect(parse(classicCrontab, crontabTest)).toEqual([JSON.parse(crontabResults)]);
    });

    it('parentheses', function() {
        // Try compiling the grammar
        var parentheses = compile(read("examples/parentheses.ne"));
        var passCases = [
            '()',
            '[(){}<>]',
            '[(((<>)()({})())(()())(())[])]',
            '<<[([])]>([(<>[]{}{}<>())[{}[][]{}{}[]<>[]{}<>{}<>[]<>{}()][[][][]()()()]({})<[]>{(){}()<>}(<>[])]())({})>'
        ];

        for (let i in passCases) {
            expect(parse(parentheses, passCases[i])).toEqual([true]);
        }

        var failCases = [
            ' ',
            '[}',
            '[(){}><]',
            '(((())))(()))'
        ];

        for (let i in failCases) {
            expect(function() { parse(parentheses, failCases[i]); }).toThrow()
        }

        // These are invalid inputs but the parser will not complain
        expect(parse(parentheses, '')).toEqual([]);
        expect(parse(parentheses, '((((())))(())()')).toEqual([]);
    });

    it('case-insensitive strings', function() {
        var caseinsensitive = compile(read("test/grammars/caseinsensitive.ne"));
        var passCases = [
            "Les rêves des amoureux sont comme le bon vin!",
            "LES RÊVES DES AMOUREUX SONT COMME LE BON VIN!",
            "leS RêVeS DeS AmOuReUx sOnT CoMmE Le bOn vIn!",
            "LEs rÊvEs dEs aMoUrEuX SoNt cOmMe lE BoN ViN!"
        ];
        passCases.forEach(function(c) {
            var p = parse(caseinsensitive, c);
            expect(p.length).toBe(1)
            expect(p[0].toUpperCase()).toBe(passCases[1]);
        });
    });

});

describe('builtins', () => {

    it('generate includes id', () => {
        const source = nearleyc(`
        X -> "hat" {% id %}
        `)
        expect(source.indexOf('function id(')).toNotBe(-1)
        const g = evalGrammar(source)
        expect(parse(g, "hat")).toEqual(["hat"])
    })

    // TODO fix docs?
    it.skip('nuller', () => {
        //@builtin "postprocessors.ne"
        const source = nearleyc(`
        ws -> " " {% nuller %}
        `)
        const g = evalGrammar(source)
        expect(parse(g, " ")).toEqual([null])
    })
})

<commit_msg>Add macro tests<commit_after>
const fs = require('fs');
const expect = require('expect');

const nearley = require('../lib/nearley');
const {compile, evalGrammar, parse, nearleyc} = require('./_shared');
const {sh, externalNearleyc, cleanup} = require('./external');

function read(filename) {
    return fs.readFileSync(filename, 'utf-8');
}

function inspect(grammar) {
  return grammar.rules.map(g => g.toString())
}

describe("bin/nearleyc", function() {
    after(cleanup)

    it('builds for ES5', function() {
        const out = externalNearleyc("grammars/parens.ne", '.js')
        var grammar = nearley.Grammar.fromCompiled(require(`./${out}.js`));
    });

    it('builds for CoffeeScript', function() {
        const out = externalNearleyc("grammars/coffeescript-test.ne", ".coffee")
        sh(`coffee -c ${out}.coffee`);
        var grammar = nearley.Grammar.fromCompiled(require(`./${out}.js`));
        expect(parse(grammar, "ABCDEFZ12309")).toEqual([ [ 'ABCDEFZ', '12309' ] ]);
    });

    it('builds for TypeScript', function() {
        this.timeout(10000); // It takes a while to run tsc!
        const out = externalNearleyc("grammars/typescript-test.ne", ".ts")
        sh(`tsc ${out}.ts`);
        var grammar = nearley.Grammar.fromCompiled(require(`./${out}.js`));
        expect(parse(grammar, "<123>")).toEqual([ [ '<', '123', '>' ] ]);
    });

})

describe('nearleyc', function() {

    it('calculator example', function() {
        var arith = compile(read("examples/calculator/arithmetic.ne"));
        expect(parse(arith, "ln (3 + 2*(8/e - sin(pi/5)))")).toEqual([ Math.log(3 + 2*(8/Math.exp(1) - Math.sin(Math.PI/5))) ]);
    });

    it('csscolor example', function() {
        var cssc = compile(read("examples/csscolor.ne"));
        expect(parse(cssc, "#FF00FF")).toEqual([{r: 0xff, g: 0x00, b: 0xff}]);
        expect(parse(cssc, "#8A7")).toEqual([{r: 0x88, g: 0xaa, b: 0x77}]);
        expect(parse(cssc, "rgb(99,66,33)")).toEqual([{r: 99, g: 66, b: 33}]);
        expect(parse(cssc, "hsl(99,66,33)")).toEqual([{h: 99, s: 66, l: 33}]);
        expect(function() { parse(cssc, "#badcolor"); }).toThrow()
    });

    it('exponential whitespace bug', function() {
        compile(read('test/grammars/indentation.ne'));
    });

    it('nullable whitespace bug', function() {
        var wsb = compile(read("test/grammars/whitespace.ne"));
        expect(parse(wsb, "(x)")).toEqual(
            [ [ [ [ '(', null, [ [ [ [ 'x' ] ] ] ], null, ')' ] ] ] ]);
    });

    it('percent bug', function() {
        compile(read('test/grammars/percent.ne'));
    });

    it('tokens', function() {
        var tokc = compile(read("examples/token.ne"));
        expect(parse(tokc, [123, 456, " ", 789])).toEqual([ [123, [ [ 456, " ", 789 ] ]] ]);
    });

    it('leo bug', function() {
        var leo = compile(read("test/grammars/leobug.ne"));
        expect(parse(leo, "baab")).toEqual(
            [ [ 'b', [], 'a', [], 'a', [ 'b' ] ],
            [ 'b', [], 'a', [], 'a', [ 'b', [] ] ] ]);
    });

    var json;
    it('json example compiles', function() {
        json = compile(read("examples/json.ne"));
    });
    it('json test1', function() {
        var test1 = read('test/grammars/test1.json');
        expect(parse(json, test1)).toEqual([JSON.parse(test1)]);
    });
    it('json test2', function() {
        var test2 = read('test/grammars/test2.json');
        expect(parse(json, test2)).toEqual([JSON.parse(test2)]);
    });

    it('tosh example', function() {
        var tosh = compile(read("examples/tosh.ne"));
        expect(parse(tosh, "set foo to 2 * e^ of ( foo * -0.05 + 0.5) * (1 - e ^ of (foo * -0.05 + 0.5))"))
            .toEqual([["setVar:to:","foo",["*",["*",2,["computeFunction:of:","e ^",["+",["*",["readVariable","foo"],-0.05],0.5]]],["-",1,["computeFunction:of:","e ^",["+",["*",["readVariable","foo"],-0.05],0.5]]]]]]);
    });

    it('classic crontab', function() {
        // Try compiling the grammar
        var classicCrontab = compile(read("examples/classic_crontab.ne"));
        // Try parsing crontab file using the newly generated parser
        var crontabTest = read('test/grammars/classic_crontab.test');
        var crontabResults = read('test/grammars/classic_crontab.results');
        expect(parse(classicCrontab, crontabTest)).toEqual([JSON.parse(crontabResults)]);
    });

    it('parentheses', function() {
        // Try compiling the grammar
        var parentheses = compile(read("examples/parentheses.ne"));
        var passCases = [
            '()',
            '[(){}<>]',
            '[(((<>)()({})())(()())(())[])]',
            '<<[([])]>([(<>[]{}{}<>())[{}[][]{}{}[]<>[]{}<>{}<>[]<>{}()][[][][]()()()]({})<[]>{(){}()<>}(<>[])]())({})>'
        ];

        for (let i in passCases) {
            expect(parse(parentheses, passCases[i])).toEqual([true]);
        }

        var failCases = [
            ' ',
            '[}',
            '[(){}><]',
            '(((())))(()))'
        ];

        for (let i in failCases) {
            expect(function() { parse(parentheses, failCases[i]); }).toThrow()
        }

        // These are invalid inputs but the parser will not complain
        expect(parse(parentheses, '')).toEqual([]);
        expect(parse(parentheses, '((((())))(())()')).toEqual([]);
    });

    it('case-insensitive strings', function() {
        var caseinsensitive = compile(read("test/grammars/caseinsensitive.ne"));
        var passCases = [
            "Les rêves des amoureux sont comme le bon vin!",
            "LES RÊVES DES AMOUREUX SONT COMME LE BON VIN!",
            "leS RêVeS DeS AmOuReUx sOnT CoMmE Le bOn vIn!",
            "LEs rÊvEs dEs aMoUrEuX SoNt cOmMe lE BoN ViN!"
        ];
        passCases.forEach(function(c) {
            var p = parse(caseinsensitive, c);
            expect(p.length).toBe(1)
            expect(p[0].toUpperCase()).toBe(passCases[1]);
        });
    });

});

describe('builtins', () => {

    it('generate includes id', () => {
        const source = nearleyc(`
        X -> "hat" {% id %}
        `)
        expect(source.indexOf('function id(')).toNotBe(-1)
        const g = evalGrammar(source)
        expect(parse(g, "hat")).toEqual(["hat"])
    })

    // TODO fix docs?
    it.skip('nuller', () => {
        //@builtin "postprocessors.ne"
        const source = nearleyc(`
        ws -> " " {% nuller %}
        `)
        const g = evalGrammar(source)
        expect(parse(g, " ")).toEqual([null])
    })
})

describe('macros', () => {

    it('seem to work', () => {
        // Matches "'Hello?' 'Hello?' 'Hello?'"
        const grammar = compile(`
            matchThree[X] -> $X " " $X " " $X
            inQuotes[X] -> "'" $X "'"
            main -> matchThree[inQuotes["Hello?"]]
        `)

        expect(inspect(grammar)).toEqual([
"main$macrocall$2$macrocall$2$string$1 → \"H\" \"e\" \"l\" \"l\" \"o\" \"?\"",
            "main$macrocall$2$macrocall$2 → main$macrocall$2$macrocall$2$string$1",
            "main$macrocall$2$macrocall$1 → \"'\" main$macrocall$2$macrocall$2 \"'\"",
            "main$macrocall$2 → main$macrocall$2$macrocall$1",
            "main$macrocall$1 → main$macrocall$2 \" \" main$macrocall$2 \" \" main$macrocall$2",
            "main → main$macrocall$1",
        ])
    })

    it('must be defined before use', () => {
        expect(() => compile(`
            main -> matchThree[inQuotes["Hello?"]]
            matchThree[X] -> $X " " $X " " $X
            inQuotes[X] -> "'" $X "'"
        `)).toThrow()
    })

})
<|endoftext|>


<commit_before>'use strict';

const crypto = require('crypto');
const co = require('co');
const hash = require('object-hash');
const ObjectStore = require('./object-store');

const NS_SEPARATOR = ':';

module.exports = class FunctionCache {

	constructor(func, options) {
		if (typeof func !== 'function') {
			throw new Error('Found no function to cache');
		}

		options = Object.assign({}, options);
		this.func = func;
		this.namespace = options.namespace || crypto.randomBytes(20).toString('hex');
		this.store = new ObjectStore({
			mongoose: options.mongoose || require('mongoose'),
			modelName: options.modelName || this.namespace
		});
	}

	get() {
		const args = arguments;
		const id = this.getId(args);

		return co(function* () {
			let result = yield this.store.get(id);

			if (result) {
				return result.data;
			}

			result = yield Promise.resolve(this.func.apply(null, args));
			this.store.set(id, result);

			return result;
		}.bind(this));
	}

	clear() {
		let id = new RegExp('^' + this.namespace + NS_SEPARATOR + '.*')
		return this.store.model.remove({id});
	}

	invalidate(query) {
		return this.store.model.remove(query);
	}

	getId(args) {
		return [this.namespace, hash([].slice.call(args))].join(NS_SEPARATOR);
	}
}<commit_msg>feat(invalidate): use own method for invalidating<commit_after>'use strict';

const crypto = require('crypto');
const co = require('co');
const hash = require('object-hash');
const ObjectStore = require('./object-store');

const NS_SEPARATOR = ':';

module.exports = class FunctionCache {

	constructor(func, options) {
		if (typeof func !== 'function') {
			throw new Error('Found no function to cache');
		}

		options = Object.assign({}, options);
		this.func = func;
		this.namespace = options.namespace || crypto.randomBytes(20).toString('hex');
		this.store = new ObjectStore({
			mongoose: options.mongoose || require('mongoose'),
			modelName: options.modelName || this.namespace
		});
	}

	get() {
		const args = arguments;
		const id = this.getId(args);

		return co(function* () {
			let result = yield this.store.get(id);

			if (result) {
				return result.data;
			}

			result = yield Promise.resolve(this.func.apply(null, args));
			this.store.set(id, result);

			return result;
		}.bind(this));
	}

	invalidate(query) {
		return this.store.model.remove(query);
	}

	clear() {
		let id = new RegExp('^' + this.namespace + NS_SEPARATOR + '.*');
		return this.invalidate({id});
	}

	getId(args) {
		return [this.namespace, hash([].slice.call(args))].join(NS_SEPARATOR);
	}
}<|endoftext|>


<commit_before>import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import { findDOMNode } from 'react-dom';
import invariant from 'invariant';

import isValidFocusKeypress from '../utils/EventUtils/isValidFocusKeypress';

const hrefables = ['a', 'area'].map(tag => `${tag}[href],`).join('');
const disableables = ['button', 'input', 'textarea', 'select'].map(tag => `${tag}:not([disabled]),`).join('');
const FOCUSABLE_QUERY = `${hrefables}${disableables}*[tabIndex]`;

/**
 * This component is used for keeping the focus within some container. When the container
 * is mounted and the `focusOnMount` prop is `true`, it will attempt to focus either:
 * - an element that matches `document.getElementById(this.props.initialFocus)`
 * - an element that matches `this._container.querySelector(this.props.initialFocus)`
 * - the first focusable element in it's children (if `this.props.initialFocus` is omitted)
 */
export default class FocusContainer extends PureComponent {
  static propTypes = {
    /**
     * The component to render as. This can be a React DOM element or
     * a react Component.
     */
    component: PropTypes.oneOfType([
      PropTypes.string,
      PropTypes.func,
    ]).isRequired,

    /**
     * An optional style to apply.
     */
    style: PropTypes.object,

    /**
     * An optional className to apply.
     */
    className: PropTypes.string,

    /**
     * The children to display.
     */
    children: PropTypes.node,

    /**
     * An optional id string or a query selector string to use for the initial focus.
     * This will only be triggered if the `focusOnMount` prop is `true`. If this is
     * omitted and the `focusOnMount` prop is `true`, the first focusable element in the
     * container will be focused.
     *
     * Examples:
     *
     * ```js
     * initialFocus="#someAmazingId"
     * // or
     * initialFocus=".md-btn,.md-list-tile"
     * ```
     */
    initialFocus: PropTypes.string,

    /**
     * Boolean if an element in the container should be focused when mounted.
     */
    focusOnMount: PropTypes.bool,

    /**
     * An optional list of additional key codes to use for focus events.
     */
    additionalFocusKeys: PropTypes.arrayOf(PropTypes.number),

    /**
     * Boolean if the focus container should start or stop containing the focus within the container.
     * This is useful for changing the focus requirements after mount.
     */
    containFocus: PropTypes.bool,
  };

  static defaultProps = {
    component: 'div',
    containFocus: true,
  };

  componentWillReceiveProps(nextProps) {
    if (this.props.containFocus === nextProps.containFocus) {
      return;
    }

    if (nextProps.containFocus) {
      this._enableFocusTrap();
      this._attemptInitialFocus();
    } else {
      this._disableFocusTrap();
    }
  }

  componentDidUpdate() {
    if (this.props.containFocus && this._container) {
      this._focusables = Array.prototype.slice.call(this._container.querySelectorAll(FOCUSABLE_QUERY))
        .filter(el => el.tabIndex !== -1);
    }
  }

  componentWillUnmount() {
    if (this.props.containFocus) {
      this._disableFocusTrap();
    }
  }

  _enableFocusTrap = () => {
    window.addEventListener('keydown', this._handleKeyDown, true);
  };

  _disableFocusTrap = () => {
    window.removeEventListener('keydown', this._handleKeyDown, true);
  };

  _attemptInitialFocus = () => {
    if (!this._container) {
      return;
    }

    const { initialFocus } = this.props;

    const toFocus = initialFocus
      ? document.getElementById(initialFocus) || this._container.querySelector(initialFocus)
      : this._focusables[0];

    let debugError;
    if (!toFocus && initialFocus) {
      debugError = ' The `initialFocus` did not match a document\'s `id` or was an invalid ';
      debugError += `\`querySelector\` for the container. \`initialFocus\`: \`${initialFocus}\`. `;
      debugError += 'If this was supposed to be an `id`, make sure to prefix with the `#` symbol.';
    }

    invariant(
      toFocus,
      'You specified that the `FocusContainer` should focus an element on mount, ' +
      'but a focusable element was not found in the children. This could be because ' +
      'the `initialFocus` prop is an invalid id or query selector, or the children ' +
      `do not contain a valid focusable element.${debugError}`
    );

    if (toFocus) {
      toFocus.focus();
    }
  };

  /**
   * Manages the event listeners to contain the focus within some container.  When the container
   * ref is not null, the container has mounted and then attempts to focus an element inside
   * if the `focusOnMount` prop is `true`.
   */
  _containFocus = (containerRef) => {
    if (containerRef === null) {
      this._container = null;
      this._disableFocusTrap();
      return;
    }

    const { focusOnMount, containFocus } = this.props;
    this._container = findDOMNode(containerRef);
    this._focusables = Array.prototype.slice.call(this._container.querySelectorAll(FOCUSABLE_QUERY))
      .filter(el => el.tabIndex !== -1);

    if (focusOnMount) {
      this._attemptInitialFocus();
    }

    if (containFocus) {
      this._enableFocusTrap();
    }
  };

  _handleKeyDown = (e) => {
    this._shifted = e.shiftKey;
    if (!isValidFocusKeypress(e, this.props.additionalFocusKeys)) {
      return;
    }

    const { target, shiftKey } = e;
    const [first, ...focusables] = this._focusables;
    const last = focusables[focusables.length - 1];

    if (shiftKey && target === first) {
      e.preventDefault();
      last.focus();
    } else if (!shiftKey && target === last) {
      e.preventDefault();
      first.focus();
    }
  };

  render() {
    const {
      component: Component,
      /* eslint-disable no-unused-vars */
      initialFocus,
      focusOnMount,
      containFocus,
      additionalFocusKeys,
      /* eslint-enable no-unused-vars */
      ...props
    } = this.props;

    return <Component {...props} ref={this._containFocus} />;
  }
}
<commit_msg>Fixed FocusContainers with only 1 focus<commit_after>import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import { findDOMNode } from 'react-dom';
import invariant from 'invariant';

import isValidFocusKeypress from '../utils/EventUtils/isValidFocusKeypress';

const hrefables = ['a', 'area'].map(tag => `${tag}[href],`).join('');
const disableables = ['button', 'input', 'textarea', 'select'].map(tag => `${tag}:not([disabled]),`).join('');
const FOCUSABLE_QUERY = `${hrefables}${disableables}*[tabIndex]`;

/**
 * This component is used for keeping the focus within some container. When the container
 * is mounted and the `focusOnMount` prop is `true`, it will attempt to focus either:
 * - an element that matches `document.getElementById(this.props.initialFocus)`
 * - an element that matches `this._container.querySelector(this.props.initialFocus)`
 * - the first focusable element in it's children (if `this.props.initialFocus` is omitted)
 */
export default class FocusContainer extends PureComponent {
  static propTypes = {
    /**
     * The component to render as. This can be a React DOM element or
     * a react Component.
     */
    component: PropTypes.oneOfType([
      PropTypes.string,
      PropTypes.func,
    ]).isRequired,

    /**
     * An optional style to apply.
     */
    style: PropTypes.object,

    /**
     * An optional className to apply.
     */
    className: PropTypes.string,

    /**
     * The children to display.
     */
    children: PropTypes.node,

    /**
     * An optional id string or a query selector string to use for the initial focus.
     * This will only be triggered if the `focusOnMount` prop is `true`. If this is
     * omitted and the `focusOnMount` prop is `true`, the first focusable element in the
     * container will be focused.
     *
     * Examples:
     *
     * ```js
     * initialFocus="#someAmazingId"
     * // or
     * initialFocus=".md-btn,.md-list-tile"
     * ```
     */
    initialFocus: PropTypes.string,

    /**
     * Boolean if an element in the container should be focused when mounted.
     */
    focusOnMount: PropTypes.bool,

    /**
     * An optional list of additional key codes to use for focus events.
     */
    additionalFocusKeys: PropTypes.arrayOf(PropTypes.number),

    /**
     * Boolean if the focus container should start or stop containing the focus within the container.
     * This is useful for changing the focus requirements after mount.
     */
    containFocus: PropTypes.bool,
  };

  static defaultProps = {
    component: 'div',
    containFocus: true,
  };

  componentWillReceiveProps(nextProps) {
    if (this.props.containFocus === nextProps.containFocus) {
      return;
    }

    if (nextProps.containFocus) {
      this._enableFocusTrap();
      this._attemptInitialFocus();
    } else {
      this._disableFocusTrap();
    }
  }

  componentDidUpdate() {
    if (this.props.containFocus && this._container) {
      this._focusables = Array.prototype.slice.call(this._container.querySelectorAll(FOCUSABLE_QUERY))
        .filter(el => el.tabIndex !== -1);
    }
  }

  componentWillUnmount() {
    if (this.props.containFocus) {
      this._disableFocusTrap();
    }
  }

  _enableFocusTrap = () => {
    window.addEventListener('keydown', this._handleKeyDown, true);
  };

  _disableFocusTrap = () => {
    window.removeEventListener('keydown', this._handleKeyDown, true);
  };

  _attemptInitialFocus = () => {
    if (!this._container) {
      return;
    }

    const { initialFocus } = this.props;

    const toFocus = initialFocus
      ? document.getElementById(initialFocus) || this._container.querySelector(initialFocus)
      : this._focusables[0];

    let debugError;
    if (!toFocus && initialFocus) {
      debugError = ' The `initialFocus` did not match a document\'s `id` or was an invalid ';
      debugError += `\`querySelector\` for the container. \`initialFocus\`: \`${initialFocus}\`. `;
      debugError += 'If this was supposed to be an `id`, make sure to prefix with the `#` symbol.';
    }

    invariant(
      toFocus,
      'You specified that the `FocusContainer` should focus an element on mount, ' +
      'but a focusable element was not found in the children. This could be because ' +
      'the `initialFocus` prop is an invalid id or query selector, or the children ' +
      `do not contain a valid focusable element.${debugError}`
    );

    if (toFocus) {
      toFocus.focus();
    }
  };

  /**
   * Manages the event listeners to contain the focus within some container.  When the container
   * ref is not null, the container has mounted and then attempts to focus an element inside
   * if the `focusOnMount` prop is `true`.
   */
  _containFocus = (containerRef) => {
    if (containerRef === null) {
      this._container = null;
      this._disableFocusTrap();
      return;
    }

    const { focusOnMount, containFocus } = this.props;
    this._container = findDOMNode(containerRef);
    this._focusables = Array.prototype.slice.call(this._container.querySelectorAll(FOCUSABLE_QUERY))
      .filter(el => el.tabIndex !== -1);

    if (focusOnMount) {
      this._attemptInitialFocus();
    }

    if (containFocus) {
      this._enableFocusTrap();
    }
  };

  _handleKeyDown = (e) => {
    this._shifted = e.shiftKey;
    if (!isValidFocusKeypress(e, this.props.additionalFocusKeys)) {
      return;
    } else if (this._focusables.length === 1) {
      e.preventDefault();
      return;
    }

    const { target, shiftKey } = e;
    const [first, ...focusables] = this._focusables;
    const last = focusables[focusables.length - 1];

    if (shiftKey && target === first) {
      e.preventDefault();
      last.focus();
    } else if (!shiftKey && target === last) {
      e.preventDefault();
      first.focus();
    }
  };

  render() {
    const {
      component: Component,
      /* eslint-disable no-unused-vars */
      initialFocus,
      focusOnMount,
      containFocus,
      additionalFocusKeys,
      /* eslint-enable no-unused-vars */
      ...props
    } = this.props;

    return <Component {...props} ref={this._containFocus} />;
  }
}
<|endoftext|>


<commit_before>import React, { Component, PropTypes } from 'react';
import ReactDOM from 'react-dom';
import PureRenderMixin from 'react-addons-pure-render-mixin';
import classnames from 'classnames';

import { TAB } from '../constants/keyCodes';

const DESKTOP_FONT_SIZE = 10;
const MOBILE_FONT_SIZE = 14;
const DESKTOP_MARGIN = 14;
const MOBILE_MARGIN = 24;

export default ComposedComponent => class Tooltip extends Component {
  constructor(props) {
    super(props);

    this.shouldComponentUpdate = PureRenderMixin.shouldComponentUpdate.bind(this);
    this.state = {
      style: null,
      textStyle: null,
      active: false,
      tabActive: false,
      mobile: false,
      timeout: null,
    };
  }

  static propTypes = {
    className: PropTypes.string,
    tooltipLabel: PropTypes.string,
    tooltipPosition: PropTypes.oneOf(['top', 'right', 'bottom', 'left']).isRequired,
    tooltipDelay: PropTypes.number.isRequired,
    onKeyUp: PropTypes.func,
    onBlur: PropTypes.func,
    onMouseOver: PropTypes.func,
    onMouseLeave: PropTypes.func,
    onTouchStart: PropTypes.func,
    onTouchEnd: PropTypes.func,
  };

  static defaultProps = {
    tooltipPosition: 'bottom',
    tooltipDelay: 0,
  };

  componentDidMount() {
    if(this.props.tooltipLabel) {
      this.hackChromeMinimumFontSize();
      window.addEventListener('resize', this.hackChromeMinimumFontSize);
    }
  }

  componentDidUpdate(prevProps) {
    const { tooltipLabel } = this.props;
    if(tooltipLabel === prevProps.tooltipLabel) { return; }

    if(tooltipLabel) {
      window.addEventListener('resize', this.hackChromeMinimumFontSize);
    } else {
      window.removeEventListener('resize', this.hackChromeMinimumFontSize);
    }
  }

  componentWillUnmount() {
    if(this.props.tooltipLabel) {
      window.removeEventListener('resize', this.hackChromeMinimumFontSize);
    }
  }

  /**
   * Chrome doesn't allow a font-size below 12px.
   * You used to be able to use -webkit-text-size-adjust: none
   * but they have dropped support for that.
   *
   * So now the solution is to scale the text if chrome only..
   */
  hackChromeMinimumFontSize = () => {
    const isChrome = navigator.userAgent.toLowerCase().indexOf('chrome') > -1;
    const fontSize = parseInt(window.getComputedStyle(this.refs.tooltipText).getPropertyValue('font-size'));

    const mobile = fontSize === MOBILE_FONT_SIZE;
    const state = { mobile };
    if(isChrome && !mobile) {
      const transform = `scale(${DESKTOP_FONT_SIZE / fontSize})`;
      state.textStyle = {
        WebkitTransform: transform,
        transform,
        transformOrigin: '51% 50%', // hack for non blurred text
      };
    }

    this.setState(state);
  };

  calcPositioningStyle = () => {
    const { tooltipPosition } = this.props;
    const margin = this.state.mobile ? MOBILE_MARGIN : DESKTOP_MARGIN;
    const control = ReactDOM.findDOMNode(this);
    const controlHeight = control.offsetHeight;
    const controlWidth = control.offsetWidth;
    const { tooltip } = this.refs;
    const tooltipWidth = tooltip.offsetWidth;
    const tooltipHeight = tooltip.offsetHeight;

    let top, right, bottom, left;
    if(tooltipPosition === 'top' || tooltipPosition === 'bottom') {
      left = (controlWidth / 2) - (tooltipWidth / 2);
    } else { // LEFT || RIGHT
      top = (controlHeight / 2) - (tooltipHeight / 2);
    }

    switch(tooltipPosition) {
      case 'top':
        top = -(tooltipHeight + margin);
        break;
      case 'right':
        left = controlWidth + margin;
        break;
      case 'bottom':
        top = margin + controlHeight;
        break;
      default:
        left = -(tooltipWidth + margin);
    }

    return {
      top,
      right,
      bottom,
      left,
    };
  };

  setActive = (key) => {
    if(!this.props.tooltipLabel || this.state.timeout) { return; }

    const timeout = setTimeout(() => {
      this.setState({
        [key]: true,
        style: this.calcPositioningStyle(),
        timeout: null,
      });
    }, this.props.tooltipDelay);

    this.setState({ timeout });
  };

  setInactive = (key) => {
    if(!this.props.tooltipLabel) { return; }

    if(this.state.timeout) {
      clearTimeout(this.state.timeout);
    }

    this.setState({
      [key]: false,
      timeout: null,
    });
  };

  handleMouseOver = (e) => {
    this.props.onMouseOver && this.props.onMouseOver(e);

    this.setActive('active');
  };

  handleMouseLeave = (e) => {
    this.props.onMouseLeave && this.props.onMouseLeave(e);

    this.setInactive('active');
  };

  handleKeyUp = (e) => {
    this.props.onKeyUp && this.props.onKeyUp(e);
    if((e.which || e.keyCode) !== TAB) { return; }

    this.setActive('tabActive');
  };

  handleBlur = (e) => {
    this.props.onBlur && this.props.onBlur(e);

    this.setInactive('tabActive');
  };

  handleTouchStart = (e) => {
    this.props.onTouchStart && this.props.onTouchStart(e);
  };

  handleTouchEnd = (e) => {
    this.props.onTouchEnd && this.props.onTouchEnd(e);
  };

  render() {
    const { style, active, tabActive, textStyle } = this.state;
    const { tooltipLabel, tooltipPosition, ...props } = this.props;
    delete props.tooltipDelay;

    const tooltip = tooltipLabel && (
      <div
        key="tooltip"
        ref="tooltip"
        className={classnames(`md-tooltip md-tooltip-${tooltipPosition}`, { 'active': active || tabActive })}
        aria-hidden={!active && !tabActive}
        style={style}
      >
        {tooltipLabel}

    );

    return (
      <ComposedComponent
        {...props}
        tooltip={tooltip}
        onMouseOver={this.handleMouseOver}
        onMouseLeave={this.handleMouseLeave}
        onKeyUp={this.handleKeyUp}
        onBlur={this.handleBlur}
        onTouchStart={this.handleTouchStart}
        onTouchEnd={this.handleTouchEnd}
      />
    );
  }
};
<commit_msg>Added docgen for injectTooltip (#81)<commit_after>import React, { Component, PropTypes } from 'react';
import ReactDOM from 'react-dom';
import PureRenderMixin from 'react-addons-pure-render-mixin';
import classnames from 'classnames';

import { TAB } from '../constants/keyCodes';

const DESKTOP_FONT_SIZE = 10;
const MOBILE_FONT_SIZE = 14;
const DESKTOP_MARGIN = 14;
const MOBILE_MARGIN = 24;

/**
 * Takes any component and injects a tooltip when the user hovers
 * over the component or touch holds it on a mobile device. It also
 * injects the event listeners and a `tooltip` prop to be added to
 * the `ComposedComponent`.
 *
 * If the `tooltipLabel` prop is omitted, the tooltip and event listeners will not
 * be included.
 *
 * ```js
 * @param ComposedComponent the component to compose with the tooltip functionality.
 * @return the ComposedComponent with a tooltip.
 * ```
 */
export default ComposedComponent => class Tooltip extends Component {
  constructor(props) {
    super(props);

    this.shouldComponentUpdate = PureRenderMixin.shouldComponentUpdate.bind(this);
    this.state = {
      style: null,
      textStyle: null,
      active: false,
      tabActive: false,
      mobile: false,
      timeout: null,
    };
  }

  static propTypes = {
    /**
     * The tooltip to display.
     */
    tooltipLabel: PropTypes.string,

    /**
     * The position of the tooltip relative to the `ComposedComponent`.
     */
    tooltipPosition: PropTypes.oneOf(['top', 'right', 'bottom', 'left']).isRequired,

    /**
     * The delay before the tooltip appears or disappears.
     */
    tooltipDelay: PropTypes.number.isRequired,

    /**
     * An optional onKeyUp function to call along with the tooltip creation onKeyUp.
     */
    onKeyUp: PropTypes.func,

    /**
     * An optional onBlur function to call along with the tooltip creation onBlur.
     */
    onBlur: PropTypes.func,

    /**
     * An optional onMouseOver function to call along with the tooltip creation onMouseOver.
     */
    onMouseOver: PropTypes.func,

    /**
     * An optional onMouseLeave function to call along with the tooltip creation onMouseLeave.
     */
    onMouseLeave: PropTypes.func,

    /**
     * An optional onTouchStart function to call along with the tooltip creation onTouchStart.
     */
    onTouchStart: PropTypes.func,

    /**
     * An optional onTouchEnd function to call along with the tooltip creation onTouchEnd.
     */
    onTouchEnd: PropTypes.func,
  };

  static defaultProps = {
    tooltipPosition: 'bottom',
    tooltipDelay: 0,
  };

  componentDidMount() {
    if(this.props.tooltipLabel) {
      this.hackChromeMinimumFontSize();
      window.addEventListener('resize', this.hackChromeMinimumFontSize);
    }
  }

  componentDidUpdate(prevProps) {
    const { tooltipLabel } = this.props;
    if(tooltipLabel === prevProps.tooltipLabel) { return; }

    if(tooltipLabel) {
      window.addEventListener('resize', this.hackChromeMinimumFontSize);
    } else {
      window.removeEventListener('resize', this.hackChromeMinimumFontSize);
    }
  }

  componentWillUnmount() {
    if(this.props.tooltipLabel) {
      window.removeEventListener('resize', this.hackChromeMinimumFontSize);
    }
  }

  /**
   * Chrome doesn't allow a font-size below 12px.
   * You used to be able to use -webkit-text-size-adjust: none
   * but they have dropped support for that.
   *
   * So now the solution is to scale the text if chrome only..
   */
  hackChromeMinimumFontSize = () => {
    const isChrome = navigator.userAgent.toLowerCase().indexOf('chrome') > -1;
    const fontSize = parseInt(window.getComputedStyle(this.refs.tooltipText).getPropertyValue('font-size'));

    const mobile = fontSize === MOBILE_FONT_SIZE;
    const state = { mobile };
    if(isChrome && !mobile) {
      const transform = `scale(${DESKTOP_FONT_SIZE / fontSize})`;
      state.textStyle = {
        WebkitTransform: transform,
        transform,
        transformOrigin: '51% 50%', // hack for non blurred text
      };
    }

    this.setState(state);
  };

  calcPositioningStyle = () => {
    const { tooltipPosition } = this.props;
    const margin = this.state.mobile ? MOBILE_MARGIN : DESKTOP_MARGIN;
    const control = ReactDOM.findDOMNode(this);
    const controlHeight = control.offsetHeight;
    const controlWidth = control.offsetWidth;
    const { tooltip } = this.refs;
    const tooltipWidth = tooltip.offsetWidth;
    const tooltipHeight = tooltip.offsetHeight;

    let top, right, bottom, left;
    if(tooltipPosition === 'top' || tooltipPosition === 'bottom') {
      left = (controlWidth / 2) - (tooltipWidth / 2);
    } else { // LEFT || RIGHT
      top = (controlHeight / 2) - (tooltipHeight / 2);
    }

    switch(tooltipPosition) {
      case 'top':
        top = -(tooltipHeight + margin);
        break;
      case 'right':
        left = controlWidth + margin;
        break;
      case 'bottom':
        top = margin + controlHeight;
        break;
      default:
        left = -(tooltipWidth + margin);
    }

    return {
      top,
      right,
      bottom,
      left,
    };
  };

  setActive = (key) => {
    if(!this.props.tooltipLabel || this.state.timeout) { return; }

    const timeout = setTimeout(() => {
      this.setState({
        [key]: true,
        style: this.calcPositioningStyle(),
        timeout: null,
      });
    }, this.props.tooltipDelay);

    this.setState({ timeout });
  };

  setInactive = (key) => {
    if(!this.props.tooltipLabel) { return; }

    if(this.state.timeout) {
      clearTimeout(this.state.timeout);
    }

    this.setState({
      [key]: false,
      timeout: null,
    });
  };

  handleMouseOver = (e) => {
    this.props.onMouseOver && this.props.onMouseOver(e);

    this.setActive('active');
  };

  handleMouseLeave = (e) => {
    this.props.onMouseLeave && this.props.onMouseLeave(e);

    this.setInactive('active');
  };

  handleKeyUp = (e) => {
    this.props.onKeyUp && this.props.onKeyUp(e);
    if((e.which || e.keyCode) !== TAB) { return; }

    this.setActive('tabActive');
  };

  handleBlur = (e) => {
    this.props.onBlur && this.props.onBlur(e);

    this.setInactive('tabActive');
  };

  handleTouchStart = (e) => {
    this.props.onTouchStart && this.props.onTouchStart(e);
  };

  handleTouchEnd = (e) => {
    this.props.onTouchEnd && this.props.onTouchEnd(e);
  };

  render() {
    const { style, active, tabActive, textStyle } = this.state;
    const { tooltipLabel, tooltipPosition, ...props } = this.props;
    delete props.tooltipDelay;
    if(!tooltipLabel) {
      return <ComposedComponent {...props} />;
    }

    const tooltip = (
      <div
        key="tooltip"
        ref="tooltip"
        className={classnames(`md-tooltip md-tooltip-${tooltipPosition}`, { 'active': active || tabActive })}
        aria-hidden={!active && !tabActive}
        style={style}
      >
        {tooltipLabel}

    );

    return (
      <ComposedComponent
        {...props}
        tooltip={tooltip}
        onMouseOver={this.handleMouseOver}
        onMouseLeave={this.handleMouseLeave}
        onKeyUp={this.handleKeyUp}
        onBlur={this.handleBlur}
        onTouchStart={this.handleTouchStart}
        onTouchEnd={this.handleTouchEnd}
      />
    );
  }
};
<|endoftext|>


<commit_before>// (C) Copyright 2014 Hewlett Packard Enterprise Development LP

import React, { Component, PropTypes } from 'react';
import ReactDOM from 'react-dom';
import KeyboardAccelerators from '../utils/KeyboardAccelerators';
import Drop from '../utils/Drop';
import Button from './Button';
import SearchIcon from './icons/base/Search';

const CLASS_ROOT = "search-input";

export default class SearchInput extends Component {

  constructor(props) {
    super(props);

    this._onInputChange = this._onInputChange.bind(this);
    this._onAddDrop = this._onAddDrop.bind(this);
    this._onRemoveDrop = this._onRemoveDrop.bind(this);
    this._onNextSuggestion = this._onNextSuggestion.bind(this);
    this._onPreviousSuggestion = this._onPreviousSuggestion.bind(this);
    this._onEnter = this._onEnter.bind(this);
    this._onClickSuggestion = this._onClickSuggestion.bind(this);
    this._onFocus = this._onFocus.bind(this);

    this.state = {
      dropActive: false,
      defaultValue: props.defaultValue,
      value: props.value,
      activeSuggestionIndex: -1
    };
  }

  componentDidUpdate (prevProps, prevState) {
    // Set up keyboard listeners appropriate to the current state.

    var activeKeyboardHandlers = {
      esc: this._onRemoveDrop,
      tab: this._onRemoveDrop,
      up: this._onPreviousSuggestion,
      down: this._onNextSuggestion,
      enter: this._onEnter
    };
    var focusedKeyboardHandlers = {
      down: this._onAddDrop
    };

    // the order here is important, need to turn off keys before turning on

    if (! this.state.focused && prevState.focused) {
      KeyboardAccelerators.stopListeningToKeyboard(this, focusedKeyboardHandlers);
    }

    if (! this.state.dropActive && prevState.dropActive) {
      document.removeEventListener('click', this._onRemoveDrop);
      KeyboardAccelerators.stopListeningToKeyboard(this, activeKeyboardHandlers);
      if (this._drop) {
        this._drop.remove();
        this._drop = null;
      }
    }

    if (this.state.focused && ! prevState.focused) {
      KeyboardAccelerators.startListeningToKeyboard(this, focusedKeyboardHandlers);
    }

    if (this.state.dropActive && ! prevState.dropActive) {
      document.addEventListener('click', this._onRemoveDrop);
      KeyboardAccelerators.startListeningToKeyboard(this, activeKeyboardHandlers);

      this._drop = Drop.add(ReactDOM.findDOMNode(this.refs.component),
        this._renderDrop(), {top: 'bottom', left: 'left'});
    } else if (this.state.dropActive && prevState.dropActive) {
      this._drop.render(this._renderDrop());
    }
  }

  componentWillUnmount () {
    document.removeEventListener('click', this._onRemoveDrop);
  }

  _onInputChange (event) {
    this.setState({dropActive: true, activeSuggestionIndex: -1});
    this.props.onChange(event.target.value, false);
  }

  _onAddDrop (event) {
    event.preventDefault();
    this.setState({dropActive: true, activeSuggestionIndex: -1});
  }

  _onRemoveDrop () {
    this.setState({dropActive: false});
  }

  _onNextSuggestion () {
    var index = this.state.activeSuggestionIndex;
    index = Math.min(index + 1, this.props.suggestions.length - 1);
    this.setState({activeSuggestionIndex: index});
  }

  _onPreviousSuggestion () {
    var index = this.state.activeSuggestionIndex;
    index = Math.max(index - 1, 0);
    this.setState({activeSuggestionIndex: index});
  }

  _onEnter () {
    this.setState({dropActive: false});
    if (this.state.activeSuggestionIndex >= 0) {
      var suggestion = this.props.suggestions[this.state.activeSuggestionIndex];
      this.setState({value: suggestion});
      this.props.onChange(suggestion, true);
    }
  }

  _onClickSuggestion (suggestion) {
    this.setState({value: suggestion, dropActive: false});
    this.props.onChange(suggestion, true);
  }

  _onFocus () {
    ReactDOM.findDOMNode(this.refs.input).select();
    this.setState({
      focused: true,
      dropActive: false,
      activeSuggestionIndex: -1
    });
  }

  _valueText (value) {
    var text = '';
    if (value) {
      if ('string' === typeof value) {
        text = value;
      } else {
        text = value.label || value.value;
      }
    }
    return text;
  }

  _renderDrop () {
    var suggestions = null;
    if (this.props.suggestions) {
      suggestions = this.props.suggestions.map(function (suggestion, index) {
        var classes = [CLASS_ROOT + "__suggestion"];
        if (index === this.state.activeSuggestionIndex) {
          classes.push(CLASS_ROOT + "__suggestion--active");
        }
        return (

            {this._valueText(suggestion)}

        );
      }, this);
    }

    return (

        {suggestions}

    );
  }

  render () {
    var classes = [CLASS_ROOT];
    if (this.state.active) {
      classes.push(CLASS_ROOT + "--active");
    }
    if (this.props.className) {
      classes.push(this.props.className);
    }

    return (

        <input ref="input" className={CLASS_ROOT + "__input"}
          id={this.props.id} name={this.props.name}
          value={this._valueText(this.props.value)}
          defaultValue={this._valueText(this.props.defaultValue)}
          placeholder={this.props.placeHolder}
          onChange={this._onInputChange}
          onFocus={this._onFocus} />
        <Button className={CLASS_ROOT + "__control"} type="icon"
          onClick={this._onAddDrop} >
          <SearchIcon />
        </Button>

    );
  }

}

SearchInput.propTypes = {
  defaultValue: PropTypes.oneOfType([
    PropTypes.shape({
      label: PropTypes.string,
      value: PropTypes.string
    }),
    PropTypes.string
  ]),
  id: PropTypes.string,
  name: PropTypes.string,
  onChange: PropTypes.func,
  placeHolder: PropTypes.string,
  suggestions: PropTypes.arrayOf(
    PropTypes.oneOfType([
      PropTypes.shape({
        label: PropTypes.string,
        value: PropTypes.string
      }),
      PropTypes.string
    ])
  ),
  value: PropTypes.oneOfType([
    PropTypes.shape({
      label: PropTypes.string,
      value: PropTypes.string
    }),
    PropTypes.string
  ])
};
<commit_msg>Added onDOMChange= and onSelect= to SearchInput.<commit_after>// (C) Copyright 2014 Hewlett Packard Enterprise Development LP

import React, { Component, PropTypes } from 'react';
import ReactDOM from 'react-dom';
import KeyboardAccelerators from '../utils/KeyboardAccelerators';
import Drop from '../utils/Drop';
import Button from './Button';
import SearchIcon from './icons/base/Search';

const CLASS_ROOT = "search-input";

export default class SearchInput extends Component {

  constructor(props) {
    super(props);

    this._onInputChange = this._onInputChange.bind(this);
    this._onAddDrop = this._onAddDrop.bind(this);
    this._onRemoveDrop = this._onRemoveDrop.bind(this);
    this._onNextSuggestion = this._onNextSuggestion.bind(this);
    this._onPreviousSuggestion = this._onPreviousSuggestion.bind(this);
    this._onEnter = this._onEnter.bind(this);
    this._onClickSuggestion = this._onClickSuggestion.bind(this);
    this._onFocus = this._onFocus.bind(this);

    this.state = {
      dropActive: false,
      defaultValue: props.defaultValue,
      value: props.value,
      activeSuggestionIndex: -1
    };
  }

  componentDidUpdate (prevProps, prevState) {
    // Set up keyboard listeners appropriate to the current state.

    var activeKeyboardHandlers = {
      esc: this._onRemoveDrop,
      tab: this._onRemoveDrop,
      up: this._onPreviousSuggestion,
      down: this._onNextSuggestion,
      enter: this._onEnter
    };
    var focusedKeyboardHandlers = {
      down: this._onAddDrop
    };

    // the order here is important, need to turn off keys before turning on

    if (! this.state.focused && prevState.focused) {
      KeyboardAccelerators.stopListeningToKeyboard(this, focusedKeyboardHandlers);
    }

    if (! this.state.dropActive && prevState.dropActive) {
      document.removeEventListener('click', this._onRemoveDrop);
      KeyboardAccelerators.stopListeningToKeyboard(this, activeKeyboardHandlers);
      if (this._drop) {
        this._drop.remove();
        this._drop = null;
      }
    }

    if (this.state.focused && ! prevState.focused) {
      KeyboardAccelerators.startListeningToKeyboard(this, focusedKeyboardHandlers);
    }

    if (this.state.dropActive && ! prevState.dropActive) {
      document.addEventListener('click', this._onRemoveDrop);
      KeyboardAccelerators.startListeningToKeyboard(this, activeKeyboardHandlers);

      this._drop = Drop.add(ReactDOM.findDOMNode(this.refs.component),
        this._renderDrop(), {top: 'bottom', left: 'left'});
    } else if (this.state.dropActive && prevState.dropActive) {
      this._drop.render(this._renderDrop());
    }
  }

  componentWillUnmount () {
    document.removeEventListener('click', this._onRemoveDrop);
  }

  _fireDOMChange () {
    var event = new Event('change', {
      'bubbles': true,
      'cancelable': true
    });
    // We use dispatchEvent to have the browser fill out the event fully.
    this.refs.input.dispatchEvent(event);
    // Manually dispatched events aren't delivered by React, so we notify too.
    this.props.onDOMChange(event);
  }

  _onInputChange (event) {
    this.setState({dropActive: true, activeSuggestionIndex: -1});
    if (this.props.onChange) {
      this.props.onChange(event.target.value, false);
    }
    if (this.props.onDOMChange) {
      this._fireDOMChange();
    }
  }

  _onAddDrop (event) {
    event.preventDefault();
    this.setState({dropActive: true, activeSuggestionIndex: -1});
  }

  _onRemoveDrop () {
    this.setState({dropActive: false});
  }

  _onNextSuggestion () {
    var index = this.state.activeSuggestionIndex;
    index = Math.min(index + 1, this.props.suggestions.length - 1);
    this.setState({activeSuggestionIndex: index});
  }

  _onPreviousSuggestion () {
    var index = this.state.activeSuggestionIndex;
    index = Math.max(index - 1, 0);
    this.setState({activeSuggestionIndex: index});
  }

  _onEnter () {
    this.setState({dropActive: false});
    if (this.state.activeSuggestionIndex >= 0) {
      var suggestion = this.props.suggestions[this.state.activeSuggestionIndex];
      this.setState({value: suggestion});
      if (this.props.onChange) {
        this.props.onChange(suggestion, true);
      }
      if (this.props.onSelect) {
        this.props.onSelect(suggestion);
      }
    }
  }

  _onClickSuggestion (suggestion) {
    this.setState({value: suggestion, dropActive: false});
    if (this.props.onChange) {
      this.props.onChange(suggestion, true);
    }
    if (this.props.onSelect) {
      this.props.onSelect(suggestion);
    }
  }

  _onFocus () {
    ReactDOM.findDOMNode(this.refs.input).select();
    this.setState({
      focused: true,
      dropActive: false,
      activeSuggestionIndex: -1
    });
  }

  _valueText (value) {
    var text = '';
    if (value) {
      if ('string' === typeof value) {
        text = value;
      } else {
        text = value.label || value.value;
      }
    }
    return text;
  }

  _renderDrop () {
    var suggestions = null;
    if (this.props.suggestions) {
      suggestions = this.props.suggestions.map(function (suggestion, index) {
        var classes = [CLASS_ROOT + "__suggestion"];
        if (index === this.state.activeSuggestionIndex) {
          classes.push(CLASS_ROOT + "__suggestion--active");
        }
        return (

            {this._valueText(suggestion)}

        );
      }, this);
    }

    return (

        {suggestions}

    );
  }

  render () {
    var classes = [CLASS_ROOT];
    if (this.state.active) {
      classes.push(CLASS_ROOT + "--active");
    }
    if (this.props.className) {
      classes.push(this.props.className);
    }

    return (

        <input ref="input" className={CLASS_ROOT + "__input"}
          id={this.props.id} name={this.props.name}
          value={this._valueText(this.props.value)}
          defaultValue={this._valueText(this.props.defaultValue)}
          placeholder={this.props.placeHolder}
          onChange={this._onInputChange}
          onFocus={this._onFocus} />
        <Button className={CLASS_ROOT + "__control"} type="icon"
          onClick={this._onAddDrop} >
          <SearchIcon />
        </Button>

    );
  }

}

SearchInput.propTypes = {
  defaultValue: PropTypes.oneOfType([
    PropTypes.shape({
      label: PropTypes.string,
      value: PropTypes.string
    }),
    PropTypes.string
  ]),
  id: PropTypes.string,
  name: PropTypes.string,
  onChange: PropTypes.func,
  onDOMChange: PropTypes.func,
  onSelect: PropTypes.func,
  placeHolder: PropTypes.string,
  suggestions: PropTypes.arrayOf(
    PropTypes.oneOfType([
      PropTypes.shape({
        label: PropTypes.string,
        value: PropTypes.string
      }),
      PropTypes.string
    ])
  ),
  value: PropTypes.oneOfType([
    PropTypes.shape({
      label: PropTypes.string,
      value: PropTypes.string
    }),
    PropTypes.string
  ])
};
<|endoftext|>


<commit_before>var http    = require('http'),
    unirest = require('unirest'),
    _       = require('lodash');

/**
 * What3Words API wrapper for the API version 1.0.
 *
 * @param apiKey The API key to access the What3Words API with
 * @param options Configuration options
 * @return Instance of {@link What3Words}
 */
function What3Words (apiKey, options) {

  if (!options) {
    options = {};
  }

  if (!apiKey) {
    throw new Error('API Key not set');
  }

  this.version     = '1.0';
  this.apiKey      = apiKey;
  this.endpoint    = options.endpoint || 'http://api.what3words.com/';
  this.language    = options.language || 'en';
  this.userAgent   = options.userAgent || 'JS Geo::What3Words';
}

module.exports = What3Words;

/**
 * Checks if oneWord is available
 * @param  {[object]}   options  Can contain the following properties:
 *          * lang: alternative language, default will be usef if this is not declared.
 *          * word: a string containing the oneWord
 *          * full: Return the full response
 */
What3Words.prototype.oneWordAvailable = function (options, callback) {
  var language = options.lang || this.language;

  this.execute('oneword-available', {word: options.word, lang: language}, function(response) {
      if (options.full) {
        callback(response);
      }

      callback(response.available);
  });
};

/**
 * Convert GPS co-ordinates into 3 words.
 * @param  {[object]}   options  Can contain the following properties:
 *          * lang: alternative language, default will be usef if this is not declared.
 *          * position: a string containing lat,long
 *          * full: Return the full response
 */
What3Words.prototype.positionToWords = function (options, callback) {
  var language = options.lang || this.language;

  this.execute('position', {position: options.position, lang: language}, function(response) {

      if (options.full) {
        callback(response);
      }

      callback(response.words.join('.'));
  });
};

/**
 * Convert 3 words or a OneWord into GPS coordinates
 * @param  {[object]}   options  Can contain the following properties:
 *          * lang: alternative language, default will be usef if this is not declared.
 *          * words: a string containing 3 dot separated words
 *          * full: Return the full response
 */
What3Words.prototype.wordsToPosition = function (options, callback) {
  var language = options.lang || this.language;

  this.execute('w3w', {string: options.words, lang: language}, function(response) {

      if (options.full) {
        callback(response);
      }

      callback(response.position.join(','));
  });
};

/**
 * Returns a list of the W3W available languages
 * @param  {[object]}   options  Can contain the following properties:
 *          * full: Return the full response
 */
What3Words.prototype.getLanguages = function (options, callback) {
  this.execute('get-languages', {}, function(response) {

      if (options.full) {
        callback(response);
      }

      callback(_.pluck(response.languages, 'code'));
  });
};

/**
 * Sends a given request as a JSON object to the W3W API and finally
 * calls the given callback function with the resulting JSON object.
 *
 * @param  {[type]}   method   W3W API method to call
 * @param  {[type]}   params   Object containg parameters to call the API with
 * @param  {Function} callback To be called on success
 */
What3Words.prototype.execute = function (method, params, callback) {
  var finalParams = _.extend({ key:  this.apiKey }, params);

  unirest.post(this.endpoint + method)
    .headers({
      'Accept': 'application/json',
      'User-Agent': this.userAgent
    })
    .send(finalParams)
    .end(function (response) {
      if (response.code !== 200) {
        throw new Error('Unable to connect to the What3Words API endpoint.');
      } else if (response.body.error) {
        throw new Error(response.body.error + '. Message: ' + response.body.message);
      }

      callback(response.body);
    });
};
<commit_msg>Add getters for private properties<commit_after>var http    = require('http'),
    unirest = require('unirest'),
    _       = require('lodash');

/**
 * What3Words API wrapper for the API version 1.0.
 *
 * @param apiKey The API key to access the What3Words API with
 * @param options Configuration options
 * @return Instance of {@link What3Words}
 */
function What3Words (apiKey, options) {

  if (!options) {
    options = {};
  }

  if (!apiKey) {
    throw new Error('API Key not set');
  }

  this.version     = '1.0';
  this.apiKey      = apiKey;
  this.endpoint    = options.endpoint || 'http://api.what3words.com/';
  this.language    = options.language || 'en';
  this.userAgent   = options.userAgent || 'JS Geo::What3Words';
}

module.exports = What3Words;

/**
 * Checks if oneWord is available
 * @param  {[object]}   options  Can contain the following properties:
 *          * lang: alternative language, default will be usef if this is not declared.
 *          * word: a string containing the oneWord
 *          * full: Return the full response
 */
What3Words.prototype.oneWordAvailable = function (options, callback) {
  var language = options.lang || this.language;

  this.execute('oneword-available', {word: options.word, lang: language}, function(response) {
      if (options.full) {
        callback(response);
      }

      callback(response.available);
  });
};

/**
 * Convert GPS co-ordinates into 3 words.
 * @param  {[object]}   options  Can contain the following properties:
 *          * lang: alternative language, default will be usef if this is not declared.
 *          * position: a string containing lat,long
 *          * full: Return the full response
 */
What3Words.prototype.positionToWords = function (options, callback) {
  var language = options.lang || this.language;

  this.execute('position', {position: options.position, lang: language}, function(response) {

      if (options.full) {
        callback(response);
      }

      callback(response.words.join('.'));
  });
};

/**
 * Convert 3 words or a OneWord into GPS coordinates
 * @param  {[object]}   options  Can contain the following properties:
 *          * lang: alternative language, default will be usef if this is not declared.
 *          * words: a string containing 3 dot separated words
 *          * full: Return the full response
 */
What3Words.prototype.wordsToPosition = function (options, callback) {
  var language = options.lang || this.language;

  this.execute('w3w', {string: options.words, lang: language}, function(response) {

      if (options.full) {
        callback(response);
      }

      callback(response.position.join(','));
  });
};

/**
 * Returns a list of the W3W available languages
 * @param  {[object]}   options  Can contain the following properties:
 *          * full: Return the full response
 */
What3Words.prototype.getLanguages = function (options, callback) {
  this.execute('get-languages', {}, function(response) {

      if (options.full) {
        callback(response);
        return 1;
      }

      callback(_.pluck(response.languages, 'code'));
  });
};

/**
 * Getters
 */
What3Words.prototype.getLanguage = function () {
  return this.language;
};

What3Words.prototype.getUserAgent = function () {
  return this.userAgent;
};

What3Words.prototype.getEndpoint = function () {
  return this.endpoint;
};

/**
 * Sends a given request as a JSON object to the W3W API and finally
 * calls the given callback function with the resulting JSON object.
 *
 * @param  {[type]}   method   W3W API method to call
 * @param  {[type]}   params   Object containg parameters to call the API with
 * @param  {Function} callback To be called on success
 */
What3Words.prototype.execute = function (method, params, callback) {
  var finalParams = _.extend({ key:  this.apiKey }, params);

  unirest.post(this.endpoint + method)
    .headers({
      'Accept': 'application/json',
      'User-Agent': this.userAgent
    })
    .send(finalParams)
    .end(function (response) {
      if (response.code !== 200) {
        throw new Error('Unable to connect to the What3Words API endpoint.');
      } else if (response.body.error) {
        throw new Error(response.body.error + '. Message: ' + response.body.message);
      }

      callback(response.body);
    });
};
<|endoftext|>


<commit_before>var geom = require('geoscript/geom');
var geotools = Packages.org.geotools;

// Supress non-critical messages
var logger = geotools.util.logging.Logging.getLogger(
    "org.geotools.referencing.factory.epsg"
);
logger.setLevel(java.util.logging.Level.WARNING);

// Force GeoTools' referencing system to use x,y order
if (!java.lang.System.getProperty("org.geotools.referencing.forceXY")) {
    java.lang.System.setProperty("org.geotools.referencing.forceXY", "true");
}
var Hints = geotools.factory.Hints;
if (!Hints.getSystemDefault(Hints.FORCE_LONGITUDE_FIRST_AXIS_ORDER)) {
    Hints.putSystemDefault(Hints.FORCE_AXIS_ORDER_HONORING, "http");
}

var GeometryTX = geotools.geometry.jts.GeometryCoordinateSequenceTransformer;
var crs = geotools.referencing.CRS;

var transform = function(geometry, from, to) {
    if (!(from instanceof Projection)) {
        from = new Projection(from);
    }
    if (!(to instanceof Projection)) {
        to = new Projection(to);
    }
    var tx = crs.findMathTransform(from._proj, to._proj);
    var gt = new GeometryTX();
    gt.mathTransform = tx;
    var jtsGeom = gt.transform(geometry._geometry);
    return geom.Geometry.fromJTS(jtsGeom);
};

var Projection = function(id) {
    var proj;
    try {
        proj = crs.decode(id);
    } catch(err) {
        try {
            proj = crs.parseWKT(id);
        } catch(err) {
            throw "Unable to create Projection object from " + id
        }
    }
    this._proj = proj;
    this.name = String(proj.name);
    this.code = crs.lookupIdentifier(proj, true) || null;
};

exports.transform = transform;
exports.Projection = Projection;
<commit_msg>Allow Projection to be created from GeoTools CRS.<commit_after>var geom = require('geoscript/geom');
var geotools = Packages.org.geotools;

// Supress non-critical messages
var logger = geotools.util.logging.Logging.getLogger(
    "org.geotools.referencing.factory.epsg"
);
logger.setLevel(java.util.logging.Level.WARNING);

// Force GeoTools' referencing system to use x,y order
if (!java.lang.System.getProperty("org.geotools.referencing.forceXY")) {
    java.lang.System.setProperty("org.geotools.referencing.forceXY", "true");
}
var Hints = geotools.factory.Hints;
if (!Hints.getSystemDefault(Hints.FORCE_LONGITUDE_FIRST_AXIS_ORDER)) {
    Hints.putSystemDefault(Hints.FORCE_AXIS_ORDER_HONORING, "http");
}

var GeometryTX = geotools.geometry.jts.GeometryCoordinateSequenceTransformer;
var crs = geotools.referencing.CRS;

var transform = function(geometry, from, to) {
    if (!(from instanceof Projection)) {
        from = new Projection(from);
    }
    if (!(to instanceof Projection)) {
        to = new Projection(to);
    }
    var gt = new GeometryTX();
    gt.mathTransform = crs.findMathTransform(from._projection, to._projection);
    var jtsGeom = gt.transform(geometry._geometry);
    return geom.Geometry.fromJTS(jtsGeom);
};

var Projection = function(id) {
    var _projection;
    // duck type GeoTools CRS
    if (typeof id.getCoordinateSystem === "function") {
        _projection = id;
    } else {
        try {
            // could be SRID
            _projection = crs.decode(id);
        } catch(err) {
            try {
                // could be Well-Known Text
                _projection = crs.parseWKT(id);
            } catch(err) {
                throw "Unable to create Projection object from " + id
            }
        }
    }
    this._projection = _projection;
    this.name = String(_projection.name);
    this.code = crs.lookupIdentifier(_projection, true) || null;
};

exports.transform = transform;
exports.Projection = Projection;
<|endoftext|>