<commit_before>var csp = require('..')

var config = require('../lib/config')
var camelize = require('camelize')
var assert = require('assert')

var SOURCELIST_DIRECTIVES = [
  'base-uri', 'child-src', 'connect-src', 'default-src', 'font-src',
  'form-action', 'frame-ancestors', 'frame-src', 'img-src',
  'manifest-src', 'media-src', 'object-src', 'prefetch-src', 'script-src',
  'style-src', 'worker-src'
]
var SOURCELISTS_WITH_UNSAFES = ['script-src', 'style-src', 'worker-src']
var SOURCELISTS_WITH_STRICT_DYNAMIC = ['default-src', 'script-src']
var BOOLEAN_DIRECTIVES = ['block-all-mixed-content', 'upgrade-insecure-requests']
var PLUGINTYPE_DIRECTIVES = ['plugin-types']
var URI_DIRECTIVES = ['report-to', 'report-uri']
var REQUIRESRIFOR_DIRECTIVES = ['require-sri-for']
var SANDBOX_DIRECTIVES = ['sandbox']

describe('with bad arguments', function () {
  describe('missing directives', function () {
    it('errors without arguments', function () {
      assertThrowsWithArg('NO_ARGUMENTS', 'csp must be called with an object argument. See the documentation.')
    })

    it('errors with non-objects', function () {
      [
        undefined,
        null,
        true,
        false,
        0,
        1,
        '',
        'str',
        [],
        [{ directives: {} }]
      ].forEach(function (arg) {
        assertThrowsWithArg(arg, 'csp must be called with an object argument. See the documentation.')
      })
    })

    it('errors with an object that has no "directives" key', function () {
      assertThrowsWithArg({}, 'csp must have at least one directive under the "directives" key. See the documentation.')
      assertThrowsWithArg({ reportOnly: true }, 'csp must have at least one directive under the "directives" key. See the documentation.')
    })

    it('errors with an empty directives object', function () {
      assertThrowsWithArg({ directives: {} }, 'csp must have at least one directive under the "directives" key. See the documentation.')
    })

    it('errors with an invalid directive type', function () {
      assertThrowsWithArg({
        directives: { 'fart-man': ['http://example.com'] }
      }, '"fart-man" is an invalid directive. See the documentation for the supported list. Force this by enabling loose mode.')
    })
  })

  it('tests all directives', function () {
    var actualDirectives = Object.keys(config.directives)
    var expectedDirectives = SOURCELIST_DIRECTIVES.concat(BOOLEAN_DIRECTIVES, PLUGINTYPE_DIRECTIVES, URI_DIRECTIVES, REQUIRESRIFOR_DIRECTIVES, SANDBOX_DIRECTIVES)
    assert.deepEqual(actualDirectives.sort(), expectedDirectives.sort())
  })

  SOURCELIST_DIRECTIVES.forEach(function (directive) {
    [directive, camelize(directive)].forEach(function (key) {
      describe(key + ' directive, a source list', function () {
        it('errors with an empty array', function () {
          assertThrowsWithDirective(key, [], directive + ' must have at least one value. To block everything, set ' + directive + ' to ["\'none\'"].')
        })

        it('errors with a non-array', function () {
          [
            null,
            undefined,
            true,
            {},
            '',
            'https://example.com',
            function () {}
          ].forEach(function (value) {
            assertThrowsWithDirective(key, value, '"' + value + '" is not a valid value for ' + directive + '. Use an array of strings.')
          })
        })

        it('errors with an array that contains non-numbers', function () {
          assertThrowsWithDirective(key, ['http://example.com', 69, 'https://example.com'], '"69" is not a valid source expression. Only non-empty strings are allowed.')
        })

        it('errors with unquoted values', function () {
          assertThrowsWithDirective(key, ['self'], '"self" must be quoted in ' + directive + '. Change it to "\'self\'" in your source list. Force this by enabling loose mode.')
          assertThrowsWithDirective(key, ['none'], '"none" must be quoted in ' + directive + '. Change it to "\'none\'" in your source list. Force this by enabling loose mode.')
          if (SOURCELISTS_WITH_UNSAFES.indexOf(directive) !== -1) {
            assertThrowsWithDirective(key, ['unsafe-inline'], '"unsafe-inline" must be quoted in ' + directive + '. Change it to "\'unsafe-inline\'" in your source list. Force this by enabling loose mode.')
            assertThrowsWithDirective(key, ['unsafe-eval'], '"unsafe-eval" must be quoted in ' + directive + '. Change it to "\'unsafe-eval\'" in your source list. Force this by enabling loose mode.')
          }
          if (SOURCELISTS_WITH_STRICT_DYNAMIC.indexOf(directive) !== -1) {
            assertThrowsWithDirective(key, ['strict-dynamic'], '"strict-dynamic" must be quoted in ' + directive + '. Change it to "\'strict-dynamic\'" in your source list. Force this by enabling loose mode.')
          }
        })

        if (SOURCELISTS_WITH_UNSAFES.indexOf(directive) === -1) {
          it('errors when called with unsafe-inline or unsafe-eval', function () {
            assertThrowsWithDirective(key, ['unsafe-inline'], '"unsafe-inline" does not make sense in ' + directive + '. Remove it.')
            assertThrowsWithDirective(key, ['unsafe-eval'], '"unsafe-eval" does not make sense in ' + directive + '. Remove it.')
            assertThrowsWithDirective(key, ["'unsafe-inline'"], '"\'unsafe-inline\'" does not make sense in ' + directive + '. Remove it.')
            assertThrowsWithDirective(key, ["'unsafe-eval'"], '"\'unsafe-eval\'" does not make sense in ' + directive + '. Remove it.')
          })
        }

        if (SOURCELISTS_WITH_STRICT_DYNAMIC.indexOf(directive) === -1) {
          it('errors when called with strict-dynamic', function () {
            assertThrowsWithDirective(key, ['strict-dynamic'], '"strict-dynamic" does not make sense in ' + directive + '. Remove it.')
            assertThrowsWithDirective(key, ["'strict-dynamic'"], '"\'strict-dynamic\'" does not make sense in ' + directive + '. Remove it.')
          })
        }
      })
    })
  })

  BOOLEAN_DIRECTIVES.forEach(function (directive) {
    [directive, camelize(directive)].forEach(function (key) {
      describe(key + ' directive, a boolean', function () {
        it('errors when called with non-boolean values', function () {
          [
            null,
            undefined,
            {},
            [],
            ['example.com'],
            123,
            '',
            'true',
            'false',
            [true]
          ].forEach(function (value) {
            assertThrowsWithDirective(key, value, '"' + value + '" is not a valid value for ' + directive + '. Use `true` or `false`.')
          })
        })
      })
    })
  })

  PLUGINTYPE_DIRECTIVES.forEach(function (directive) {
    [directive, camelize(directive)].forEach(function (key) {
      describe(key + ' directive', function () {
        it('errors with an empty array', function () {
          assertThrowsWithDirective(key, [], directive + ' must have at least one value. To block everything, set ' + directive + ' to ["\'none\'"].')
        })

        it('errors with a non-array', function () {
          [
            null,
            undefined,
            true,
            {},
            '',
            'https://example.com',
            function () {}
          ].forEach(function (value) {
            assertThrowsWithDirective(key, value, '"' + value + '" is not a valid value for ' + directive + '. Use an array of strings.')
          })
        })

        it('errors when called with an array that contains non-strings', function () {
          assertThrowsWithDirective(key, ['application/x-shockwave-flash', 420], '"420" is not a valid plugin type. Only non-empty strings are allowed.')
        })

        it('errors with unquoted values', function () {
          assertThrowsWithDirective(key, ['none'], '"none" must be quoted in ' + directive + '. Change it to "\'none\'" in your source list. Force this by enabling loose mode.')
        })

        it("errors when called with values that don't make sense, like 'self', 'unsafe-inline', and 'unsafe-eval'", function () {
          assertThrowsWithDirective(key, ['self'], '"self" does not make sense in ' + directive + '. Remove it.')
          assertThrowsWithDirective(key, ['unsafe-inline'], '"unsafe-inline" does not make sense in ' + directive + '. Remove it.')
          assertThrowsWithDirective(key, ['unsafe-eval'], '"unsafe-eval" does not make sense in ' + directive + '. Remove it.')
          assertThrowsWithDirective(key, ["'self'"], '"\'self\'" does not make sense in ' + directive + '. Remove it.')
          assertThrowsWithDirective(key, ["'unsafe-inline'"], '"\'unsafe-inline\'" does not make sense in ' + directive + '. Remove it.')
          assertThrowsWithDirective(key, ["'unsafe-eval'"], '"\'unsafe-eval\'" does not make sense in ' + directive + '. Remove it.')
        })
      })
    })
  })

  URI_DIRECTIVES.forEach(function (directive) {
    [directive, camelize(directive)].forEach(function (key) {
      describe(key + ' directive, a URI', function () {
        it('errors when called with non-string values', function () {
          [
            null,
            undefined,
            {},
            { length: 0 },
            { length: 2 },
            [],
            ['example.com'],
            123,
            true,
            ''
          ].forEach(function (value) {
            assertThrowsWithDirective(key, value, '"' + value + '" is not a valid value for ' + directive + '. Use a non-empty string.')
          })
        })
      })
    })
  })

  assert.deepEqual(REQUIRESRIFOR_DIRECTIVES, ['require-sri-for'])

  REQUIRESRIFOR_DIRECTIVES.forEach(function (directive) {
    [directive, camelize(directive)].forEach(function (key) {
      describe(key + ' directive, a require-sri-for', function () {
        it('errors with an empty array', function () {
          assertThrowsWithDirective(key, [], 'require-sri-for must have at least one value. To require nothing, omit the directive.')
        })

        it('errors with a non-array', function () {
          [
            null,
            undefined,
            true,
          {},
            '',
            'script',
            function () {}
          ].forEach(function (value) {
            assertThrowsWithDirective(key, value, '"' + value + '" is not a valid value for require-sri-for. Use an array of strings.')
          })
        })

        it('errors when called with unsupported directives', function () {
          assertThrowsWithDirective(key, [undefined], '"undefined" is not a valid require-sri-for value. Remove it.')
          assertThrowsWithDirective(key, ['script', undefined], '"undefined" is not a valid require-sri-for value. Remove it.')
          assertThrowsWithDirective(key, ['style', 123], '"123" is not a valid require-sri-for value. Remove it.')
          assertThrowsWithDirective(key, ['style', 'self'], '"self" is not a valid require-sri-for value. Remove it.')
          assertThrowsWithDirective(key, ["'none'", 'script'], '"\'none\'" is not a valid require-sri-for value. Remove it.')
        })
      })
    })
  })

  describe('sandbox directive', function () {
    it('is the only directive of its type', function () {
      assert.deepEqual(SANDBOX_DIRECTIVES, ['sandbox'])
    })

    it('errors with an empty array', function () {
      assertThrowsWithDirective('sandbox', [], 'sandbox must have at least one value. To block everything, set sandbox to `true`.')
    })

    it('errors when called with non-array values', function () {
      [
        null,
        undefined,
        {},
        '',
        0,
        1
      ].forEach(function (value) {
        assertThrowsWithDirective('sandbox', value, '"' + value + '" is not a valid value for sandbox. Use an array of strings or `true`.')
      })
    })

    it('errors when called with unsupported directives', function () {
      assertThrowsWithDirective('sandbox', ['allow-forms', undefined], '"undefined" is not a valid sandbox directive. Remove it.')
      assertThrowsWithDirective('sandbox', ['allow-forms', 123], '"123" is not a valid sandbox directive. Remove it.')
      assertThrowsWithDirective('sandbox', ['allow-foo'], '"allow-foo" is not a valid sandbox directive. Remove it.')
      assertThrowsWithDirective('sandbox', ['self'], '"self" is not a valid sandbox directive. Remove it.')
      assertThrowsWithDirective('sandbox', ["'self'"], '"\'self\'" is not a valid sandbox directive. Remove it.')
      assertThrowsWithDirective('sandbox', ['none'], '"none" is not a valid sandbox directive. Remove it.')
      assertThrowsWithDirective('sandbox', ["'none'"], '"\'none\'" is not a valid sandbox directive. Remove it.')
    })
  })
})

function assertThrowsWithArg (arg, expectedMessage) {
  assert.throws(
    function () {
      if (arg === 'NO_ARGUMENTS') {
        csp()
      } else {
        csp(arg)
      }
    },
    function (err) {
      var isError = err instanceof Error
      var isMessageOk = err.message === expectedMessage
      return isError && isMessageOk
    },
    'Calling with ' + arg + ' did not throw'
  )
}

function assertThrowsWithDirective (directiveKey, directiveValue, expectedMessage) {
  var arg = { directives: {} }
  arg.directives[directiveKey] = directiveValue
  assertThrowsWithArg(arg, expectedMessage)
}
<commit_msg>Minor: fix indentation issues in test<commit_after>var csp = require('..')

var config = require('../lib/config')
var camelize = require('camelize')
var assert = require('assert')

var SOURCELIST_DIRECTIVES = [
  'base-uri', 'child-src', 'connect-src', 'default-src', 'font-src',
  'form-action', 'frame-ancestors', 'frame-src', 'img-src',
  'manifest-src', 'media-src', 'object-src', 'prefetch-src', 'script-src',
  'style-src', 'worker-src'
]
var SOURCELISTS_WITH_UNSAFES = ['script-src', 'style-src', 'worker-src']
var SOURCELISTS_WITH_STRICT_DYNAMIC = ['default-src', 'script-src']
var BOOLEAN_DIRECTIVES = ['block-all-mixed-content', 'upgrade-insecure-requests']
var PLUGINTYPE_DIRECTIVES = ['plugin-types']
var URI_DIRECTIVES = ['report-to', 'report-uri']
var REQUIRESRIFOR_DIRECTIVES = ['require-sri-for']
var SANDBOX_DIRECTIVES = ['sandbox']

describe('with bad arguments', function () {
  describe('missing directives', function () {
    it('errors without arguments', function () {
      assertThrowsWithArg('NO_ARGUMENTS', 'csp must be called with an object argument. See the documentation.')
    })

    it('errors with non-objects', function () {
      [
        undefined,
        null,
        true,
        false,
        0,
        1,
        '',
        'str',
        [],
        [{ directives: {} }]
      ].forEach(function (arg) {
        assertThrowsWithArg(arg, 'csp must be called with an object argument. See the documentation.')
      })
    })

    it('errors with an object that has no "directives" key', function () {
      assertThrowsWithArg({}, 'csp must have at least one directive under the "directives" key. See the documentation.')
      assertThrowsWithArg({ reportOnly: true }, 'csp must have at least one directive under the "directives" key. See the documentation.')
    })

    it('errors with an empty directives object', function () {
      assertThrowsWithArg({ directives: {} }, 'csp must have at least one directive under the "directives" key. See the documentation.')
    })

    it('errors with an invalid directive type', function () {
      assertThrowsWithArg({
        directives: { 'fart-man': ['http://example.com'] }
      }, '"fart-man" is an invalid directive. See the documentation for the supported list. Force this by enabling loose mode.')
    })
  })

  it('tests all directives', function () {
    var actualDirectives = Object.keys(config.directives)
    var expectedDirectives = SOURCELIST_DIRECTIVES.concat(BOOLEAN_DIRECTIVES, PLUGINTYPE_DIRECTIVES, URI_DIRECTIVES, REQUIRESRIFOR_DIRECTIVES, SANDBOX_DIRECTIVES)
    assert.deepEqual(actualDirectives.sort(), expectedDirectives.sort())
  })

  SOURCELIST_DIRECTIVES.forEach(function (directive) {
    [directive, camelize(directive)].forEach(function (key) {
      describe(key + ' directive, a source list', function () {
        it('errors with an empty array', function () {
          assertThrowsWithDirective(key, [], directive + ' must have at least one value. To block everything, set ' + directive + ' to ["\'none\'"].')
        })

        it('errors with a non-array', function () {
          [
            null,
            undefined,
            true,
            {},
            '',
            'https://example.com',
            function () {}
          ].forEach(function (value) {
            assertThrowsWithDirective(key, value, '"' + value + '" is not a valid value for ' + directive + '. Use an array of strings.')
          })
        })

        it('errors with an array that contains non-numbers', function () {
          assertThrowsWithDirective(key, ['http://example.com', 69, 'https://example.com'], '"69" is not a valid source expression. Only non-empty strings are allowed.')
        })

        it('errors with unquoted values', function () {
          assertThrowsWithDirective(key, ['self'], '"self" must be quoted in ' + directive + '. Change it to "\'self\'" in your source list. Force this by enabling loose mode.')
          assertThrowsWithDirective(key, ['none'], '"none" must be quoted in ' + directive + '. Change it to "\'none\'" in your source list. Force this by enabling loose mode.')
          if (SOURCELISTS_WITH_UNSAFES.indexOf(directive) !== -1) {
            assertThrowsWithDirective(key, ['unsafe-inline'], '"unsafe-inline" must be quoted in ' + directive + '. Change it to "\'unsafe-inline\'" in your source list. Force this by enabling loose mode.')
            assertThrowsWithDirective(key, ['unsafe-eval'], '"unsafe-eval" must be quoted in ' + directive + '. Change it to "\'unsafe-eval\'" in your source list. Force this by enabling loose mode.')
          }
          if (SOURCELISTS_WITH_STRICT_DYNAMIC.indexOf(directive) !== -1) {
            assertThrowsWithDirective(key, ['strict-dynamic'], '"strict-dynamic" must be quoted in ' + directive + '. Change it to "\'strict-dynamic\'" in your source list. Force this by enabling loose mode.')
          }
        })

        if (SOURCELISTS_WITH_UNSAFES.indexOf(directive) === -1) {
          it('errors when called with unsafe-inline or unsafe-eval', function () {
            assertThrowsWithDirective(key, ['unsafe-inline'], '"unsafe-inline" does not make sense in ' + directive + '. Remove it.')
            assertThrowsWithDirective(key, ['unsafe-eval'], '"unsafe-eval" does not make sense in ' + directive + '. Remove it.')
            assertThrowsWithDirective(key, ["'unsafe-inline'"], '"\'unsafe-inline\'" does not make sense in ' + directive + '. Remove it.')
            assertThrowsWithDirective(key, ["'unsafe-eval'"], '"\'unsafe-eval\'" does not make sense in ' + directive + '. Remove it.')
          })
        }

        if (SOURCELISTS_WITH_STRICT_DYNAMIC.indexOf(directive) === -1) {
          it('errors when called with strict-dynamic', function () {
            assertThrowsWithDirective(key, ['strict-dynamic'], '"strict-dynamic" does not make sense in ' + directive + '. Remove it.')
            assertThrowsWithDirective(key, ["'strict-dynamic'"], '"\'strict-dynamic\'" does not make sense in ' + directive + '. Remove it.')
          })
        }
      })
    })
  })

  BOOLEAN_DIRECTIVES.forEach(function (directive) {
    [directive, camelize(directive)].forEach(function (key) {
      describe(key + ' directive, a boolean', function () {
        it('errors when called with non-boolean values', function () {
          [
            null,
            undefined,
            {},
            [],
            ['example.com'],
            123,
            '',
            'true',
            'false',
            [true]
          ].forEach(function (value) {
            assertThrowsWithDirective(key, value, '"' + value + '" is not a valid value for ' + directive + '. Use `true` or `false`.')
          })
        })
      })
    })
  })

  PLUGINTYPE_DIRECTIVES.forEach(function (directive) {
    [directive, camelize(directive)].forEach(function (key) {
      describe(key + ' directive', function () {
        it('errors with an empty array', function () {
          assertThrowsWithDirective(key, [], directive + ' must have at least one value. To block everything, set ' + directive + ' to ["\'none\'"].')
        })

        it('errors with a non-array', function () {
          [
            null,
            undefined,
            true,
            {},
            '',
            'https://example.com',
            function () {}
          ].forEach(function (value) {
            assertThrowsWithDirective(key, value, '"' + value + '" is not a valid value for ' + directive + '. Use an array of strings.')
          })
        })

        it('errors when called with an array that contains non-strings', function () {
          assertThrowsWithDirective(key, ['application/x-shockwave-flash', 420], '"420" is not a valid plugin type. Only non-empty strings are allowed.')
        })

        it('errors with unquoted values', function () {
          assertThrowsWithDirective(key, ['none'], '"none" must be quoted in ' + directive + '. Change it to "\'none\'" in your source list. Force this by enabling loose mode.')
        })

        it("errors when called with values that don't make sense, like 'self', 'unsafe-inline', and 'unsafe-eval'", function () {
          assertThrowsWithDirective(key, ['self'], '"self" does not make sense in ' + directive + '. Remove it.')
          assertThrowsWithDirective(key, ['unsafe-inline'], '"unsafe-inline" does not make sense in ' + directive + '. Remove it.')
          assertThrowsWithDirective(key, ['unsafe-eval'], '"unsafe-eval" does not make sense in ' + directive + '. Remove it.')
          assertThrowsWithDirective(key, ["'self'"], '"\'self\'" does not make sense in ' + directive + '. Remove it.')
          assertThrowsWithDirective(key, ["'unsafe-inline'"], '"\'unsafe-inline\'" does not make sense in ' + directive + '. Remove it.')
          assertThrowsWithDirective(key, ["'unsafe-eval'"], '"\'unsafe-eval\'" does not make sense in ' + directive + '. Remove it.')
        })
      })
    })
  })

  URI_DIRECTIVES.forEach(function (directive) {
    [directive, camelize(directive)].forEach(function (key) {
      describe(key + ' directive, a URI', function () {
        it('errors when called with non-string values', function () {
          [
            null,
            undefined,
            {},
            { length: 0 },
            { length: 2 },
            [],
            ['example.com'],
            123,
            true,
            ''
          ].forEach(function (value) {
            assertThrowsWithDirective(key, value, '"' + value + '" is not a valid value for ' + directive + '. Use a non-empty string.')
          })
        })
      })
    })
  })

  assert.deepEqual(REQUIRESRIFOR_DIRECTIVES, ['require-sri-for'])

  REQUIRESRIFOR_DIRECTIVES.forEach(function (directive) {
    [directive, camelize(directive)].forEach(function (key) {
      describe(key + ' directive, a require-sri-for', function () {
        it('errors with an empty array', function () {
          assertThrowsWithDirective(key, [], 'require-sri-for must have at least one value. To require nothing, omit the directive.')
        })

        it('errors with a non-array', function () {
          [
            null,
            undefined,
            true,
            {},
            '',
            'script',
            function () {}
          ].forEach(function (value) {
            assertThrowsWithDirective(key, value, '"' + value + '" is not a valid value for require-sri-for. Use an array of strings.')
          })
        })

        it('errors when called with unsupported directives', function () {
          assertThrowsWithDirective(key, [undefined], '"undefined" is not a valid require-sri-for value. Remove it.')
          assertThrowsWithDirective(key, ['script', undefined], '"undefined" is not a valid require-sri-for value. Remove it.')
          assertThrowsWithDirective(key, ['style', 123], '"123" is not a valid require-sri-for value. Remove it.')
          assertThrowsWithDirective(key, ['style', 'self'], '"self" is not a valid require-sri-for value. Remove it.')
          assertThrowsWithDirective(key, ["'none'", 'script'], '"\'none\'" is not a valid require-sri-for value. Remove it.')
        })
      })
    })
  })

  describe('sandbox directive', function () {
    it('is the only directive of its type', function () {
      assert.deepEqual(SANDBOX_DIRECTIVES, ['sandbox'])
    })

    it('errors with an empty array', function () {
      assertThrowsWithDirective('sandbox', [], 'sandbox must have at least one value. To block everything, set sandbox to `true`.')
    })

    it('errors when called with non-array values', function () {
      [
        null,
        undefined,
        {},
        '',
        0,
        1
      ].forEach(function (value) {
        assertThrowsWithDirective('sandbox', value, '"' + value + '" is not a valid value for sandbox. Use an array of strings or `true`.')
      })
    })

    it('errors when called with unsupported directives', function () {
      assertThrowsWithDirective('sandbox', ['allow-forms', undefined], '"undefined" is not a valid sandbox directive. Remove it.')
      assertThrowsWithDirective('sandbox', ['allow-forms', 123], '"123" is not a valid sandbox directive. Remove it.')
      assertThrowsWithDirective('sandbox', ['allow-foo'], '"allow-foo" is not a valid sandbox directive. Remove it.')
      assertThrowsWithDirective('sandbox', ['self'], '"self" is not a valid sandbox directive. Remove it.')
      assertThrowsWithDirective('sandbox', ["'self'"], '"\'self\'" is not a valid sandbox directive. Remove it.')
      assertThrowsWithDirective('sandbox', ['none'], '"none" is not a valid sandbox directive. Remove it.')
      assertThrowsWithDirective('sandbox', ["'none'"], '"\'none\'" is not a valid sandbox directive. Remove it.')
    })
  })
})

function assertThrowsWithArg (arg, expectedMessage) {
  assert.throws(
    function () {
      if (arg === 'NO_ARGUMENTS') {
        csp()
      } else {
        csp(arg)
      }
    },
    function (err) {
      var isError = err instanceof Error
      var isMessageOk = err.message === expectedMessage
      return isError && isMessageOk
    },
    'Calling with ' + arg + ' did not throw'
  )
}

function assertThrowsWithDirective (directiveKey, directiveValue, expectedMessage) {
  var arg = { directives: {} }
  arg.directives[directiveKey] = directiveValue
  assertThrowsWithArg(arg, expectedMessage)
}
<|endoftext|>


<commit_before>
var fieldDictionary = require('./decoder')
var templateArray = new Array()

/*
Templates
*/
function parseTemplates(templateObject){
	templateObject.templates.forEach(function(element, index, array){

		//Logging
		console.log("Template for " + element.id + ": " + element.fields.length + " fields")

		//Build the template object
		var templateFields = new Array()

		// Iterate through the fields in the original packet and transform them into fieldType Objects
		element.fields.forEach(function(field, index2, array2){

			//Get a reference to the dictionary's object
			var dictionaryElement = fieldDictionary[field.fieldType]

			// null protection, in case of propietary fields we can't parse
			if (typeof dictionaryElement !== 'undefined'){

				//Add to the array a new object, a copy of the dictionary's one.
				templateFields[index2] = {
					length: field.fieldLength,
					decode: dictionaryElement.decode,
					name: dictionaryElement.name
				}
			}
		})

		//Save to templateArray
		templateArray[element.id] = templateFields

		// Log Results
		console.log(templateFields)
	})
}

/*
Options
*/
function parseOptions(optionsObject){
	console.log("Template Options: Length " + optionsObject.length)
}

/*
Flowset Data
*/
function parseFlowset(flowsetObject, packetHeader, metadata){

	//Log
	console.log("Flowset " + flowsetObject.flowset_id + ": Length " + flowsetObject.length)

	//Build the reception date
	var date = new Date(0)
	date.setUTCSeconds(packetHeader.unix_secs)

	// Return the result as a structure
	var result = {}
	result.source_address = metadata.address
	result.source_id = packetHeader.source_id
	result.time = date
	result.data = new Array()

	// Get the correponding template, if it exists.
	var templateFields = templateArray[flowsetObject.flowset_id]
	if (typeof templateFields !== 'undefined'){

		var currentPosition = 0

		// Iterate through the fields
		templateFields.forEach(function(field, index, array){

			// Get the raw data corresponding to this field
			var currentBuffer = flowsetObject.flowdata.slice(currentPosition, currentPosition+field.length)

			//Build the corresponding result object
			result.data[index] = {}
			result.data[index].name = field.name

			// Parse the field, if we have a 'decode' function available
			if (typeof field.decode !== 'undefined'){
				result.data[index].value = field.decode(currentBuffer)
			} else {
				result.data[index].value = currentBuffer
			}

			//Move the counter
			currentPosition = currentPosition + field.length
		})

		//Log the result
		console.log(result)
	} else {

		//No data yet
		console.log("template not found")
	}
}

/*
Entry function
*/
function mainFunction(packet) {

	//Parse date
	var date = new Date(0)
	date.setUTCSeconds(packet.header.unix_secs)
	var receptionDate = date.toGMTString()

	//Show Log
	console.log("\n"+ receptionDate + ": Received packet from source ("
		+ " id: " + packet.header.source_id
		+ " ip: " + packet.metadata.address + ")" )

	//Process the packets only if we are reading Netflow V9
	if (packet.header.version == 9){

		packet.v9Flowsets.forEach(function(element, index, array){

			if (element.flowset_id == 0){

				//Template Fields description
				parseTemplates(element)
			} else if (element.flowset_id == 1) {

				//Template Options
				parseOptions(element)
			} else {

				//Data
				parseFlowset(element, packet.header, packet.metadata)

			}
		})

	}

}

//Exporting
module.exports = mainFunction<commit_msg>Cleaned up result generation code and added "field_type" value on each field.<commit_after>
var fieldDictionary = require('./decoder')
var templateArray = new Array()

/*
Templates
*/
function parseTemplates(templateObject){
	templateObject.templates.forEach(function(element, index, array){

		//Logging
		console.log("Template for " + element.id + ": " + element.fields.length + " fields")

		//Build the template object
		var templateFields = new Array()

		// Iterate through the fields in the original packet and transform them into fieldType Objects
		element.fields.forEach(function(field, index2, array2){

			//Get a reference to the dictionary's object
			var dictionaryElement = fieldDictionary[field.fieldType]

			// null protection, in case of propietary fields we can't parse
			if (typeof dictionaryElement !== 'undefined'){

				//Add to the array a new object, a copy of the dictionary's one.
				templateFields[index2] = {
					length: field.fieldLength,
					decode: dictionaryElement.decode,
					name: dictionaryElement.name,
					field_type: field.fieldType
				}
			}
		})

		//Save to templateArray
		templateArray[element.id] = templateFields

		// Log Results
		console.log(templateFields)
	})
}

/*
Options
*/
function parseOptions(optionsObject){
	console.log("Template Options: Length " + optionsObject.length)
}

/*
Flowset Data
*/
function parseFlowset(flowsetObject, packetHeader, metadata){

	//Log
	console.log("Flowset " + flowsetObject.flowset_id + ": Length " + flowsetObject.length)

	//Build the reception date
	var date = new Date(0)
	date.setUTCSeconds(packetHeader.unix_secs)

	// Return the result as a structure
	var result = {}
	result.source_address = metadata.address
	result.source_id = packetHeader.source_id
	result.time = date
	result.data = new Array()

	// Get the correponding template, if it exists.
	var templateFields = templateArray[flowsetObject.flowset_id]
	if (typeof templateFields !== 'undefined'){

		var currentPosition = 0

		// Iterate through the fields
		templateFields.forEach(function(field, index, array){

			// Get the raw data corresponding to this field
			var currentBuffer = flowsetObject.flowdata.slice(currentPosition, currentPosition+field.length)

			// Parse the field, if we have a 'decode' function available
			var parsedValue = (typeof field.decode !== 'undefined')?
				field.decode(currentBuffer):
				currentBuffer;

			//Build the corresponding result object
			result.data[index] = {
				name: field.name,
				field_type: field.field_type,
				value: parsedValue
			}

			//Move the counter
			currentPosition = currentPosition + field.length
		})

		//Log the result
		console.log(result)
	} else {

		//No data yet
		console.log("template not found")
	}
}

/*
Entry function
*/
function mainFunction(packet) {

	//Parse date
	var date = new Date(0)
	date.setUTCSeconds(packet.header.unix_secs)
	var receptionDate = date.toGMTString()

	//Show Log
	console.log("\n"+ receptionDate + ": Received packet from source ("
		+ " id: " + packet.header.source_id
		+ " ip: " + packet.metadata.address + ")" )

	//Process the packets only if we are reading Netflow V9
	if (packet.header.version == 9){

		packet.v9Flowsets.forEach(function(element, index, array){

			if (element.flowset_id == 0){

				//Template Fields description
				parseTemplates(element)
			} else if (element.flowset_id == 1) {

				//Template Options
				parseOptions(element)
			} else {

				//Data
				parseFlowset(element, packet.header, packet.metadata)

			}
		})

	}

}

//Exporting
module.exports = mainFunction<|endoftext|>


<commit_before>// Builders for classes related to MultiFilePatches.

import {TextBuffer} from 'atom';
import MultiFilePatch from '../../lib/models/patch/multi-file-patch';
import FilePatch from '../../lib/models/patch/file-patch';
import File from '../../lib/models/patch/file';
import Patch from '../../lib/models/patch/patch';
import Hunk from '../../lib/models/patch/hunk';
import {Unchanged, Addition, Deletion, NoNewline} from '../../lib/models/patch/region';

class LayeredBuffer {
  constructor() {
    this.buffer = new TextBuffer();
    this.layers = ['patch', 'hunk', 'unchanged', 'addition', 'deletion', 'noNewline'].reduce((layers, name) => {
      layers[name] = this.buffer.addMarkerLayer();
      return layers;
    }, {});
  }

  getInsertionPoint() {
    return this.buffer.getEndPosition();
  }

  getLayer(markerLayerName) {
    const layer = this.layers[markerLayerName];
    if (!layer) {
      throw new Error(`invalid marker layer name: ${markerLayerName}`);
    }
    return layer;
  }

  appendMarked(markerLayerName, lines) {
    const startPosition = this.buffer.getEndPosition();
    const layer = this.getLayer(markerLayerName);
    this.buffer.append(lines.join('\n'));
    const marker = layer.markRange([startPosition, this.buffer.getEndPosition()], {exclusive: true});
    this.buffer.append('\n');
    return marker;
  }

  markFrom(markerLayerName, startPosition) {
    const endPosition = this.buffer.getEndPosition().translate([-1, Infinity]);
    const layer = this.getLayer(markerLayerName);
    return layer.markRange([startPosition, endPosition], {exclusive: true});
  }

  wrapReturn(object) {
    return {
      buffer: this.buffer,
      layers: this.layers,
      ...object,
    };
  }
}

class MultiFilePatchBuilder {
  constructor(layeredBuffer = null) {
    this.layeredBuffer = layeredBuffer;

    this.filePatches = [];
  }

  addFilePatch(block) {
    const filePatch = new FilePatchBuilder(this.layeredBuffer);
    block(filePatch);
    this.filePatches.push(filePatch.build().filePatch);
    return this;
  }

  build() {
    return this.layeredBuffer.wrapReturn({
      multiFilePatch: new MultiFilePatch({
        buffer: this.layeredBuffer.buffer,
        layers: this.layeredBuffer.layers,
        filePatches: this.filePatches,
      }),
    });
  }
}

class FilePatchBuilder {
  constructor(layeredBuffer = null) {
    this.layeredBuffer = layeredBuffer;

    this.oldFile = new File({path: 'file', mode: '100644'});
    this.newFile = null;

    this.patchBuilder = new PatchBuilder(this.layeredBuffer);
  }

  setOldFile(block) {
    const file = new FileBuilder();
    block(file);
    this.oldFile = file.build().file;
    return this;
  }

  setNewFile(block) {
    const file = new FileBuilder();
    block(file);
    this.newFile = file.build().file;
    return this;
  }

  status(...args) {
    this.patchBuilder.status(...args);
    return this;
  }

  addHunk(...args) {
    this.patchBuilder.addHunk(...args);
    return this;
  }

  build() {
    const {patch} = this.patchBuilder.build();

    if (this.newFile === null) {
      this.newFile = this.oldFile.clone();
    }

    return this.layeredBuffer.wrapReturn({
      filePatch: new FilePatch(this.oldFile, this.newFile, patch),
    });
  }
}

class FileBuilder {
  constructor() {
    this._path = 'file.txt';
    this._mode = '100644';
    this._symlink = null;
  }

  path(thePath) {
    this._path = thePath;
    return this;
  }

  mode(theMode) {
    this._mode = theMode;
    return this;
  }

  executable() {
    return this.mode('100755');
  }

  symlinkTo(destinationPath) {
    this._symlink = destinationPath;
    return this.mode('120000');
  }

  build() {
    return {file: new File({path: this._path, mode: this._mode, symlink: this._symlink})};
  }
}

class PatchBuilder {
  constructor(layeredBuffer = null) {
    this.layeredBuffer = layeredBuffer;

    this.status = 'modified';
    this.hunks = [];

    this.patchStart = this.layeredBuffer.getInsertionPoint();
    this.drift = 0;
  }

  status(st) {
    if (['modified', 'added', 'deleted'].indexOf(st) === -1) {
      throw new Error(`Unrecognized status: ${st} (must be 'modified', 'added' or 'deleted')`);
    }

    this.status = st;
    return this;
  }

  addHunk(block) {
    const builder = new HunkBuilder(this.layeredBuffer, this.drift);
    block(builder);
    const {hunk, drift} = builder.build();
    this.hunks.push(hunk);
    this.drift = drift;
    return this;
  }

  build() {
    if (this.hunks.length === 0) {
      this.addHunk(hunk => hunk.oldRow(1).unchanged('0000').added('0001').deleted('0002').unchanged('0003'));
      this.addHunk(hunk => hunk.oldRow(10).unchanged('0004').added('0005').deleted('0006').unchanged('0007'));
    }

    const marker = this.layeredBuffer.markFrom('patch', this.patchStart);

    return this.layeredBuffer.wrapReturn({
      patch: new Patch({status: this.status, hunks: this.hunks, marker}),
    });
  }
}

class HunkBuilder {
  constructor(layeredBuffer = null, drift = 0) {
    this.layeredBuffer = layeredBuffer;
    this.drift = drift;

    this.oldStartRow = 0;
    this.oldRowCount = null;
    this.newStartRow = null;
    this.newRowCount = null;

    this.sectionHeading = "don't care";

    this.hunkStartPoint = this.layeredBuffer.getInsertionPoint();
    this.regions = [];
  }

  oldRow(rowNumber) {
    this.oldStartRow = rowNumber;
    return this;
  }

  unchanged(...lines) {
    this.regions.push(new Unchanged(this.layeredBuffer.appendMarked('unchanged', lines)));
    return this;
  }

  added(...lines) {
    this.regions.push(new Addition(this.layeredBuffer.appendMarked('addition', lines)));
    return this;
  }

  deleted(...lines) {
    this.regions.push(new Deletion(this.layeredBuffer.appendMarked('deletion', lines)));
    return this;
  }

  noNewline() {
    this.regions.push(new NoNewline(this.layeredBuffer.appendMarked('noNewline', ' No newline at end of file')));
    return this;
  }

  build() {
    if (this.regions.length === 0) {
      this.unchanged('0000').added('0001').deleted('0002').unchanged('0003');
    }

    if (this.oldRowCount === null) {
      this.oldRowCount = this.regions.reduce((count, region) => region.when({
        unchanged: () => count + region.bufferRowCount(),
        deletion: () => count + region.bufferRowCount(),
        default: () => count,
      }), 0);
    }

    if (this.newStartRow === null) {
      this.newStartRow = this.oldStartRow + this.drift;
    }

    if (this.newRowCount === null) {
      this.newRowCount = this.regions.reduce((count, region) => region.when({
        unchanged: () => count + region.bufferRowCount(),
        addition: () => count + region.bufferRowCount(),
        default: () => count,
      }), 0);
    }

    const marker = this.layeredBuffer.markFrom('hunk', this.hunkStartPoint);

    return this.layeredBuffer.wrapReturn({
      hunk: new Hunk({
        oldStartRow: this.oldStartRow,
        oldRowCount: this.oldRowCount,
        newStartRow: this.newStartRow,
        newRowCount: this.newRowCount,
        sectionHeading: this.sectionHeading,
        marker,
        regions: this.regions,
      }),
      drift: this.drift + this.newRowCount - this.oldRowCount,
    });
  }
}

export function buildMultiFilePatch() {
  return new MultiFilePatchBuilder(new LayeredBuffer());
}

export function buildFilePatch() {
  return new FilePatchBuilder(new LayeredBuffer());
}

export function buildPatch() {
  return new PatchBuilder(new LayeredBuffer());
}

export function buildHunk() {
  return new HunkBuilder(new LayeredBuffer());
}
<commit_msg>Allow nullFiles in FilePatches<commit_after>// Builders for classes related to MultiFilePatches.

import {TextBuffer} from 'atom';
import MultiFilePatch from '../../lib/models/patch/multi-file-patch';
import FilePatch from '../../lib/models/patch/file-patch';
import File, {nullFile} from '../../lib/models/patch/file';
import Patch from '../../lib/models/patch/patch';
import Hunk from '../../lib/models/patch/hunk';
import {Unchanged, Addition, Deletion, NoNewline} from '../../lib/models/patch/region';

class LayeredBuffer {
  constructor() {
    this.buffer = new TextBuffer();
    this.layers = ['patch', 'hunk', 'unchanged', 'addition', 'deletion', 'noNewline'].reduce((layers, name) => {
      layers[name] = this.buffer.addMarkerLayer();
      return layers;
    }, {});
  }

  getInsertionPoint() {
    return this.buffer.getEndPosition();
  }

  getLayer(markerLayerName) {
    const layer = this.layers[markerLayerName];
    if (!layer) {
      throw new Error(`invalid marker layer name: ${markerLayerName}`);
    }
    return layer;
  }

  appendMarked(markerLayerName, lines) {
    const startPosition = this.buffer.getEndPosition();
    const layer = this.getLayer(markerLayerName);
    this.buffer.append(lines.join('\n'));
    const marker = layer.markRange([startPosition, this.buffer.getEndPosition()], {exclusive: true});
    this.buffer.append('\n');
    return marker;
  }

  markFrom(markerLayerName, startPosition) {
    const endPosition = this.buffer.getEndPosition().translate([-1, Infinity]);
    const layer = this.getLayer(markerLayerName);
    return layer.markRange([startPosition, endPosition], {exclusive: true});
  }

  wrapReturn(object) {
    return {
      buffer: this.buffer,
      layers: this.layers,
      ...object,
    };
  }
}

class MultiFilePatchBuilder {
  constructor(layeredBuffer = null) {
    this.layeredBuffer = layeredBuffer;

    this.filePatches = [];
  }

  addFilePatch(block) {
    const filePatch = new FilePatchBuilder(this.layeredBuffer);
    block(filePatch);
    this.filePatches.push(filePatch.build().filePatch);
    return this;
  }

  build() {
    return this.layeredBuffer.wrapReturn({
      multiFilePatch: new MultiFilePatch({
        buffer: this.layeredBuffer.buffer,
        layers: this.layeredBuffer.layers,
        filePatches: this.filePatches,
      }),
    });
  }
}

class FilePatchBuilder {
  constructor(layeredBuffer = null) {
    this.layeredBuffer = layeredBuffer;

    this.oldFile = new File({path: 'file', mode: '100644'});
    this.newFile = null;

    this.patchBuilder = new PatchBuilder(this.layeredBuffer);
  }

  setOldFile(block) {
    const file = new FileBuilder();
    block(file);
    this.oldFile = file.build().file;
    return this;
  }

  nullOldFile() {
    this.oldFile = nullFile;
    return this;
  }

  setNewFile(block) {
    const file = new FileBuilder();
    block(file);
    this.newFile = file.build().file;
    return this;
  }

  nullNewFile() {
    this.newFile = nullFile;
    return this;
  }

  status(...args) {
    this.patchBuilder.status(...args);
    return this;
  }

  addHunk(...args) {
    this.patchBuilder.addHunk(...args);
    return this;
  }

  build() {
    const {patch} = this.patchBuilder.build();

    if (this.newFile === null) {
      this.newFile = this.oldFile.clone();
    }

    return this.layeredBuffer.wrapReturn({
      filePatch: new FilePatch(this.oldFile, this.newFile, patch),
    });
  }
}

class FileBuilder {
  constructor() {
    this._path = 'file.txt';
    this._mode = '100644';
    this._symlink = null;
  }

  path(thePath) {
    this._path = thePath;
    return this;
  }

  mode(theMode) {
    this._mode = theMode;
    return this;
  }

  executable() {
    return this.mode('100755');
  }

  symlinkTo(destinationPath) {
    this._symlink = destinationPath;
    return this.mode('120000');
  }

  build() {
    return {file: new File({path: this._path, mode: this._mode, symlink: this._symlink})};
  }
}

class PatchBuilder {
  constructor(layeredBuffer = null) {
    this.layeredBuffer = layeredBuffer;

    this.status = 'modified';
    this.hunks = [];

    this.patchStart = this.layeredBuffer.getInsertionPoint();
    this.drift = 0;
  }

  status(st) {
    if (['modified', 'added', 'deleted'].indexOf(st) === -1) {
      throw new Error(`Unrecognized status: ${st} (must be 'modified', 'added' or 'deleted')`);
    }

    this.status = st;
    return this;
  }

  addHunk(block) {
    const builder = new HunkBuilder(this.layeredBuffer, this.drift);
    block(builder);
    const {hunk, drift} = builder.build();
    this.hunks.push(hunk);
    this.drift = drift;
    return this;
  }

  build() {
    if (this.hunks.length === 0) {
      this.addHunk(hunk => hunk.oldRow(1).unchanged('0000').added('0001').deleted('0002').unchanged('0003'));
      this.addHunk(hunk => hunk.oldRow(10).unchanged('0004').added('0005').deleted('0006').unchanged('0007'));
    }

    const marker = this.layeredBuffer.markFrom('patch', this.patchStart);

    return this.layeredBuffer.wrapReturn({
      patch: new Patch({status: this.status, hunks: this.hunks, marker}),
    });
  }
}

class HunkBuilder {
  constructor(layeredBuffer = null, drift = 0) {
    this.layeredBuffer = layeredBuffer;
    this.drift = drift;

    this.oldStartRow = 0;
    this.oldRowCount = null;
    this.newStartRow = null;
    this.newRowCount = null;

    this.sectionHeading = "don't care";

    this.hunkStartPoint = this.layeredBuffer.getInsertionPoint();
    this.regions = [];
  }

  oldRow(rowNumber) {
    this.oldStartRow = rowNumber;
    return this;
  }

  unchanged(...lines) {
    this.regions.push(new Unchanged(this.layeredBuffer.appendMarked('unchanged', lines)));
    return this;
  }

  added(...lines) {
    this.regions.push(new Addition(this.layeredBuffer.appendMarked('addition', lines)));
    return this;
  }

  deleted(...lines) {
    this.regions.push(new Deletion(this.layeredBuffer.appendMarked('deletion', lines)));
    return this;
  }

  noNewline() {
    this.regions.push(new NoNewline(this.layeredBuffer.appendMarked('noNewline', ' No newline at end of file')));
    return this;
  }

  build() {
    if (this.regions.length === 0) {
      this.unchanged('0000').added('0001').deleted('0002').unchanged('0003');
    }

    if (this.oldRowCount === null) {
      this.oldRowCount = this.regions.reduce((count, region) => region.when({
        unchanged: () => count + region.bufferRowCount(),
        deletion: () => count + region.bufferRowCount(),
        default: () => count,
      }), 0);
    }

    if (this.newStartRow === null) {
      this.newStartRow = this.oldStartRow + this.drift;
    }

    if (this.newRowCount === null) {
      this.newRowCount = this.regions.reduce((count, region) => region.when({
        unchanged: () => count + region.bufferRowCount(),
        addition: () => count + region.bufferRowCount(),
        default: () => count,
      }), 0);
    }

    const marker = this.layeredBuffer.markFrom('hunk', this.hunkStartPoint);

    return this.layeredBuffer.wrapReturn({
      hunk: new Hunk({
        oldStartRow: this.oldStartRow,
        oldRowCount: this.oldRowCount,
        newStartRow: this.newStartRow,
        newRowCount: this.newRowCount,
        sectionHeading: this.sectionHeading,
        marker,
        regions: this.regions,
      }),
      drift: this.drift + this.newRowCount - this.oldRowCount,
    });
  }
}

export function buildMultiFilePatch() {
  return new MultiFilePatchBuilder(new LayeredBuffer());
}

export function buildFilePatch() {
  return new FilePatchBuilder(new LayeredBuffer());
}

export function buildPatch() {
  return new PatchBuilder(new LayeredBuffer());
}

export function buildHunk() {
  return new HunkBuilder(new LayeredBuffer());
}
<|endoftext|>


<commit_before>'use strict';

const test = require('tape');
const sinon = require('../sinon');

require('css-modules-require-hook/preset');

global.window = {
    addEventListener: sinon.stub(),
};

const mock = require('mock-require');

const connect = sinon.stub().returns({
    on: sinon.stub(),
});

const Terminal = sinon.stub().returns({
    open: sinon.stub(),
    fit: sinon.stub(),
    on: sinon.stub(),
    proposeGeometry: sinon.stub().returns({
        cols: 80,
        rows: 25,
    }),
});

mock('socket.io-client/dist/socket.io.min', {
    connect,
});

mock('xterm/dist/xterm', Terminal);

const gritty = require('../../client/gritty');

test('gritty: Terminal: new', (t) => {
    before();

    gritty();
    t.ok(Terminal.calledWithNew(), 'should have been called with new');
    after();

    t.end();
});

test('gritty: Terminal: args', (t) => {
    before();
    const args = {
        scrollback: 1000,
        tabStopWidth: 4,
        theme: 'gritty',
    };

    gritty();

    t.ok(Terminal.calledWith(args), 'should have been called with args');

    after();
    t.end();
});

function before() {
    global.location = {};
}

function after() {
    delete global.location;
}
<commit_msg>chore(gritty) rm "\n"<commit_after>'use strict';

const test = require('tape');
const sinon = require('../sinon');

require('css-modules-require-hook/preset');

global.window = {
    addEventListener: sinon.stub(),
};

const mock = require('mock-require');

const connect = sinon.stub().returns({
    on: sinon.stub(),
});

const Terminal = sinon.stub().returns({
    open: sinon.stub(),
    fit: sinon.stub(),
    on: sinon.stub(),
    proposeGeometry: sinon.stub().returns({
        cols: 80,
        rows: 25,
    }),
});

mock('socket.io-client/dist/socket.io.min', {
    connect,
});

mock('xterm/dist/xterm', Terminal);

const gritty = require('../../client/gritty');

test('gritty: Terminal: new', (t) => {
    before();

    gritty();
    t.ok(Terminal.calledWithNew(), 'should have been called with new');
    after();

    t.end();
});

test('gritty: Terminal: args', (t) => {
    before();
    const args = {
        scrollback: 1000,
        tabStopWidth: 4,
        theme: 'gritty',
    };

    gritty();

    t.ok(Terminal.calledWith(args), 'should have been called with args');

    after();
    t.end();
});

function before() {
    global.location = {};
}

function after() {
    delete global.location;
}
<|endoftext|>


<commit_before>import {Points, Point} from './points';

export class Recogniser {

    /**
     * An API similar to a scikit learn recogniser
     */

    constructor() {
    }

    /**
     * @param features: a list of features, each feature is a list of the three points that represent a tangible.
     * @param targets: an array integers which represent the class of tangible.
     */

    fit(features, targets) {
        this.features = features;
        this.targets = targets;
    }

    /**
     *
     * @param points: a feature to predict a tangible from, a set of three touch points.
     * @returns []
     */

    predict(points) {
        let matches = [];

        // We need to have minimum of three touch points to identify the Visual and it's orientation.
        if (points.length == 3) {
            let touchSorted = Points.sortClockwise(points);
            let touchDistA = Point.distance(touchSorted[0], touchSorted[1]);
            let touchDistB = Point.distance(touchSorted[0], touchSorted[2]);
            let touchDistC = Point.distance(touchSorted[1], touchSorted[2]);

            for (let i = 0; i < this.features.length; i++) {
                let feature = this.features[i];

                if (feature.length == 3) {
                    let regPointsDists = Points.sortClockwise(feature);
                    let regDistA = Point.distance(regPointsDists[0], regPointsDists[1]);
                    let regDistB = Point.distance(regPointsDists[0], regPointsDists[2]);
                    let regDistC = Point.distance(regPointsDists[1], regPointsDists[2]);
                    let similarity = Math.abs(touchDistA - regDistA) + Math.abs(touchDistB - regDistB) + Math.abs(touchDistC - regDistC);
                    matches.push({target: this.targets[i], similarity: similarity});
                }
            }

            matches.sort(function (a, b) {
                if (a.similarity < b.similarity)
                    return -1;
                if (a.similarity > b.similarity)
                    return 1;
                return 0;
            });

            return matches;
        }

        return []; //No tangible was recognised
    };
}<commit_msg>Algorithm documentation.<commit_after>import {Points, Point} from './points';

export class Recogniser {

    /**
     * An API similar to a scikit learn recogniser
     */

    constructor() {
    }

    /**
     * @param features: a list of features, each feature is a list of the three points that represent a tangible.
     * @param targets: an array integers which represent the class of tangible.
     */

    fit(features, targets) {
        this.features = features;
        this.targets = targets;
    }

    /**
     * The recognition algorithm computes the similarity between a triangle (three touch points) and an array of features,
     * each feature being three touch points that represent a particular tangible.
     *
     * To consistently compare triangles, the algorithm finds the centroid of each triangle, and then finds the point
     * with the furthest distance from the centre. It then sorts the points that make up each triangle clockwise
     * and computes the similarity of each triangle by seeing how closely the lengths of each side match.
     *
     * @param points: a feature to predict a tangible from, a set of three touch points.
     * @returns []: an array of targets and their similarity to the detected touch points.
     */

    predict(points) {
        let matches = [];

        // We need to have minimum of three touch points to identify the Visual and it's orientation.
        if (points.length == 3) {
            let touchSorted = Points.sortClockwise(points); // Sort the detected points in clockwise order. The point
            // with the furthest distance from the centroid of the triangle is first. We do this so that when we
            // consistently compare the lengths of the triangle sides.

            // Find the length of each side of the detected triangle
            let touchDistA = Point.distance(touchSorted[0], touchSorted[1]);
            let touchDistB = Point.distance(touchSorted[0], touchSorted[2]);
            let touchDistC = Point.distance(touchSorted[1], touchSorted[2]);

            for (let i = 0; i < this.features.length; i++) {
                let feature = this.features[i];

                if (feature.length == 3) {
                    let regPointsDists = Points.sortClockwise(feature); // Sort the features points in clockwise order.
                    // The point with the furthest distance from the centroid of the triangle is first. We do this so
                    // that when we consistently compare the lengths of the triangle sides.

                    // Find the length of each side of the feature triangle
                    let regDistA = Point.distance(regPointsDists[0], regPointsDists[1]);
                    let regDistB = Point.distance(regPointsDists[0], regPointsDists[2]);
                    let regDistC = Point.distance(regPointsDists[1], regPointsDists[2]);

                    // Compute the similarity metric: The sum of the differences between the sides of the touch point
                    // triangle and the feature triangle.
                    let similarity = Math.abs(touchDistA - regDistA) + Math.abs(touchDistB - regDistB) + Math.abs(touchDistC - regDistC);
                    matches.push({target: this.targets[i], similarity: similarity});
                }
            }

            // Sort the matches by their similarity. Highest similarity comes first.
            matches.sort(function (a, b) {
                if (a.similarity < b.similarity)
                    return -1;
                if (a.similarity > b.similarity)
                    return 1;
                return 0;
            });

            return matches;
        }

        return []; //No tangible was recognised
    };
}<|endoftext|>


<commit_before>import { UserPresence } from 'meteor/socialize:user-presence'
import { Mongo } from 'meteor/mongo'
import { developmentValidationEnabledFalse, UsersStats } from '../../indexDB'
import SimpleSchema from 'simpl-schema'

var stats = UsersStats.find().fetch()
if (stats.length != 3){
  UsersStats.remove({})
  UsersStats.insert({_id: "connected", connected: 0, userIds: []})
  UsersStats.insert({_id: "created", created: Meteor.users.find().count()})
  UsersStats.insert({_id: "lastMonth", created: Meteor.users.find({}).fetch().filter(i => new Date(i.createdAt) > (new Date().getTime() - 1000*60*60*24*30) /* 30 days */).length})
}

//used to prevent edits to connected field parsed from userIds field on every running instance when observer triggers
var connectedLength = new Set(UsersStats.findOne("connected", {fields: {userIds: 1}}).userIds)

UsersStats.find({_id: "connected"}, {fields: {userIds: 1}}).observeChanges({
  changed(id, fields){
    if (connectedLength.size == fields.userIds.length){
    } else {
      connectedLength = new Set(fields.userIds)
    }
    return
  }
})

//will fire several times per user thus requires observer
UserPresence.onUserOnline(function(userId, connection){
  var len = connectedLength.size
  connectedLength.add(userId)
  if (len != connectedLength.size){
    UsersStats.update("connected", {$addToSet: {userIds: userId}, $inc: {connected: 1}})
  }
})

UserPresence.onUserOffline(function (userId) {
  var len = connectedLength.size
  connectedLength.delete(userId)
  if (len != connectedLength.size){
    UsersStats.update("connected", {$pull: {userIds: userId}, $inc: {connected: -1}})
  }
})

Meteor.startup(() => {
  SyncedCron.add({
      name: 'Count newly registered users',
      schedule: (parser) => parser.cron('0 */3 * * *'), // every 3 hours will be sufficient
      job: () => Meteor.call('signedUpLastMonth', (err, data) => {
        UsersStats.update({
          _id: 'lastMonth'
        }, {
          $set: {
            created: data || 0
          }
        })
      })
  })

  SyncedCron.add({
      name: 'Fetch moderator candidates',
      schedule: (parser) => parser.cron('0 1 * * *'), // every 24 hours at 1am
      job: () => Meteor.call('possibleModerators', (err, data) => {})
  })
})
<commit_msg>Problem: [/] Users online is always 0 (#1651)<commit_after>import { UserPresence } from 'meteor/socialize:user-presence'
import { Mongo } from 'meteor/mongo'
import { developmentValidationEnabledFalse, UsersStats } from '../../indexDB'
import SimpleSchema from 'simpl-schema'

var stats = UsersStats.find().fetch()
if (stats.length != 3){
  UsersStats.remove({})
  UsersStats.insert({_id: "connected", connected: 0, userIds: []})
  UsersStats.insert({_id: "created", created: Meteor.users.find().count()})
  UsersStats.insert({_id: "lastMonth", created: Meteor.users.find({}).fetch().filter(i => new Date(i.createdAt) > (new Date().getTime() - 1000*60*60*24*30) /* 30 days */).length})
}

//used to prevent edits to connected field parsed from userIds field on every running instance when observer triggers
/*var connectedLength = new Set(UsersStats.findOne("connected", {fields: {userIds: 1}}).userIds)

UsersStats.find({_id: "connected"}, {fields: {userIds: 1}}).observeChanges({
  changed(id, fields){
    if (connectedLength.size == fields.userIds.length){
    } else {
      connectedLength = new Set(fields.userIds)
    }
    return
  }
})*/

//will fire several times per user thus requires observer
UserPresence.onUserOnline(function(userId, connection){
  //var len = connectedLength.size
  //connectedLength.add(userId)
  //if (len != connectedLength.size){
  if (!~((UsersStats.findOne('connected') || {}).userIds || []).indexOf(userId)) { // if it's not already on the list, add it
    UsersStats.update("connected", {$addToSet: {userIds: userId}, $inc: {connected: 1}})
  }
  //}
})

UserPresence.onUserOffline(function (userId) {
  //var len = connectedLength.size
  //connectedLength.delete(userId)
  //if (len != connectedLength.size){
  if (~((UsersStats.findOne('connected') || {}).userIds || []).indexOf(userId)) { // if it was on the list, remove it
    UsersStats.update("connected", {$pull: {userIds: userId}, $inc: {connected: -1}})
  }
  //}
})

Meteor.startup(() => {
  SyncedCron.add({
      name: 'Count newly registered users',
      schedule: (parser) => parser.cron('0 */3 * * *'), // every 3 hours will be sufficient
      job: () => Meteor.call('signedUpLastMonth', (err, data) => {
        UsersStats.update({
          _id: 'lastMonth'
        }, {
          $set: {
            created: data || 0
          }
        })
      })
  })

  SyncedCron.add({
      name: 'Fetch moderator candidates',
      schedule: (parser) => parser.cron('0 1 * * *'), // every 24 hours at 1am
      job: () => Meteor.call('possibleModerators', (err, data) => {})
  })
})
<|endoftext|>


<commit_before>import React from 'react';
import { Link } from 'react-router';
import { Grid, Row, Col, FormGroup, ControlLabel, FormControl, Button } from 'react-bootstrap';
import Dialog from 'material-ui/Dialog';
import FlatButton from 'material-ui/FlatButton';
import RaisedButton from 'material-ui/RaisedButton';
import TextField from 'material-ui/TextField';

import { handleLogin, handleFacebookLogin, handleGoogleLogin, handleIVLELogin } from '../../../modules/login';

export default class Login extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      errors: {},
    };
  }

  handleSubmit(event) {
    event.preventDefault();
  }

  componentDidUpdate(prevProps, prevState) {
    const self = this;

    // Necessary because the real DOM hasn't been updated at this point, only the virtual DOM. lol....
    if (prevProps.open != this.props.open)
      setTimeout(() => {
        self.setState({ errors: {} });

        handleLogin({
          component: self,
          clearErrors() {
            self.setState({ errors: {} });
          },
          setError(er, text) {
            let errorsObject = {};
            errorsObject[er] = text;
            errorsObject = $.extend({}, self.state.errors, errorsObject);
            self.setState({ errors: errorsObject });
          },
          afterLogin() {
            self.props.closeLogin();
          },
        });
      });
  }

  socialLoginHandler(handler) {
    const self = this;

    return (event) => handler({
        afterLogin() {
          self.props.closeLogin();
        }
      });
  }

  render() {
    return (
      <Dialog
        open={ this.props.open }
        onRequestClose={ this.props.closeLogin }
        contentStyle={{ maxWidth: 550 }}>
        <form ref="login" className="login" onSubmit={ this.handleSubmit }>
          <Grid fluid={true}>
            <Row>
              <Col>

Login to Free4All

              </Col>
            </Row>
            <Row className="openid" style={{ marginBottom: 20 }}>
              <Col xs={12} sm={4}>
                <FlatButton
                  style={{ width: "100%", backgroundColor: "#ff8c00" }}
                  labelColor="#ffffff"
                  label="NUS OpenID"
                  onTouchTap={ this.socialLoginHandler(handleIVLELogin) } />
              </Col>
              <Col xs={12} sm={4}>
                <FlatButton
                  style={{ width: "100%", backgroundColor: "#395697" }} l
                  abelColor="#ffffff"
                  label="Facebook"
                  icon={  }
                  onTouchTap={ this.socialLoginHandler(handleFacebookLogin) } />
              </Col>
              <Col xs={12} sm={4}>
                <FlatButton
                  style={{ width: "100%", backgroundColor: "#e0492f" }}
                  labelColor="#ffffff"
                  label="Google"
                  icon={  }
                  onTouchTap={ this.socialLoginHandler(handleGoogleLogin) } />
              </Col>
            </Row>
            <Row>
              <Col xs={12}>

                  OR

              </Col>
            </Row>
            <Row>
              <Col xs={ 6 }>
                <TextField
                  type="email"
                  ref="emailAddress"
                  name="emailAddress"
                  hintText="Email Address"
                  errorText={ this.state.errors.emailAddress }
                />
              </Col>
              <Col xs={ 6 }>

Not yet a member? <Link to="/signup">Sign up</Link>

              </Col>
            </Row>
            <Row>
              <Col xs={ 6 }>
                <TextField
                  type="password"
                  ref="password"
                  name="password"
                  hintText="Password"
                  errorText={ this.state.errors.password }
                />
              </Col>
              <Col xs={ 6 }>

<Link to="/recover-password">Forgot Password?</Link>

              </Col>
            </Row>
            <Row>
              <Col xs={ 12 }>
              </Col>
            </Row>
            <Row>
              <Col xs={6}>
                <RaisedButton type="submit" fullWidth={true} primary={true} label="Login" />
              </Col>
            </Row>
          </Grid>
        </form>
      </Dialog>
    );
  }
}
<commit_msg>Make login dialog mobile friendly<commit_after>import React from 'react';
import { Link } from 'react-router';
import { Grid, Row, Col, FormGroup, ControlLabel, FormControl, Button } from 'react-bootstrap';
import Dialog from 'material-ui/Dialog';
import FlatButton from 'material-ui/FlatButton';
import RaisedButton from 'material-ui/RaisedButton';
import TextField from 'material-ui/TextField';

import { handleLogin, handleFacebookLogin, handleGoogleLogin, handleIVLELogin } from '../../../modules/login';

export default class Login extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      errors: {},
    };
  }

  handleSubmit(event) {
    event.preventDefault();
  }

  componentDidUpdate(prevProps, prevState) {
    const self = this;

    // Necessary because the real DOM hasn't been updated at this point, only the virtual DOM. lol....
    if (prevProps.open != this.props.open)
      setTimeout(() => {
        self.setState({ errors: {} });

        handleLogin({
          component: self,
          clearErrors() {
            self.setState({ errors: {} });
          },
          setError(er, text) {
            let errorsObject = {};
            errorsObject[er] = text;
            errorsObject = $.extend({}, self.state.errors, errorsObject);
            self.setState({ errors: errorsObject });
          },
          afterLogin() {
            self.props.closeLogin();
          },
        });
      });
  }

  socialLoginHandler(handler) {
    const self = this;

    return (event) => handler({
        afterLogin() {
          self.props.closeLogin();
        }
      });
  }

  render() {
    return (
      <Dialog
        open={ this.props.open }
        onRequestClose={ this.props.closeLogin }
        contentStyle={{ maxWidth: 550 }}
        autoScrollBodyContent={true}>
        <form ref="login" className="login" onSubmit={ this.handleSubmit }>
          <Grid fluid={true}>
            <Row>
              <Col>

Login to Free4All

              </Col>
            </Row>
            <Row className="openid" style={{ marginBottom: 10 }}>
              <Col xs={12} sm={4} smOffset={0}>
                <FlatButton
                  style={{ width: "100%", backgroundColor: "#ff8c00", marginBottom: 10 }}
                  labelColor="#ffffff"
                  label="NUS OpenID"
                  onTouchTap={ this.socialLoginHandler(handleIVLELogin) } />
              </Col>
              <Col xs={12} sm={4} smOffset={0}>
                <FlatButton
                  style={{ width: "100%", backgroundColor: "#395697", marginBottom: 10 }}
                  labelColor="#ffffff"
                  label="Facebook"
                  icon={  }
                  onTouchTap={ this.socialLoginHandler(handleFacebookLogin) } />
              </Col>
              <Col xs={12} sm={4} smOffset={0}>
                <FlatButton
                  style={{ width: "100%", backgroundColor: "#e0492f", marginBottom: 10 }}
                  labelColor="#ffffff"
                  label="Google"
                  icon={  }
                  onTouchTap={ this.socialLoginHandler(handleGoogleLogin) } />
              </Col>
            </Row>
            <Row>
              <Col xs={12}>

                  OR

              </Col>
            </Row>
            <Row>
              <Col xs={12} sm={6}>
                <TextField
                  type="email"
                  ref="emailAddress"
                  name="emailAddress"
                  hintText="Email Address"
                  errorText={ this.state.errors.emailAddress }
                  fullWidth={true}
                />
                <TextField
                  type="password"
                  ref="password"
                  name="password"
                  hintText="Password"
                  errorText={ this.state.errors.password }
                  fullWidth={true}
                />
              </Col>
              <Col xs={12} sm={6}>

Not yet a member? <Link to="/signup">Sign up</Link>

<Link to="/recover-password">Forgot Password?</Link>

              </Col>
            </Row>
            <Row style={{ marginTop: 20 }}>
              <Col xs={6} xsOffset={3} sm={3} smOffset={9}>
                <RaisedButton type="submit" style={{ width: "100%" }} primary={true} label="Login" />
              </Col>
            </Row>
          </Grid>
        </form>
      </Dialog>
    );
  }
}
<|endoftext|>


<commit_before><commit_msg>Added a thing<commit_after><|endoftext|>


<commit_before>/* @flow */
import React from 'react';
import { FlatList } from 'react-native';
import { Toast } from 'native-base';

import LoadingMessage from 'app/LoadingMessage';
import PosterCard from 'app/PosterCard';
import Empty from './Empty';
import type { Poster } from 'app/types';
import type { State as NavigationState } from 'app/reducers/navigation';

type Props = {|
  errored: boolean,
  loading: boolean,
  navigation: NavigationState,
  posters: Array<Poster>,
  toastComponent: typeof Toast,
|};

class PosterCardList extends React.Component {
  props: Props;

  static defaultProps = {
    toastComponent: Toast,
  };

  renderItem = (item: { item: Poster }) => {
    return <PosterCard poster={item.item} navigation={this.props.navigation} />;
  };

  render() {
    const { errored, loading, posters, toastComponent } = this.props;

    if (errored) {
      toastComponent.show({
        text: 'Failed to load the poster. Please try again later.',
        position: 'bottom',
        buttonText: 'Dismiss',
      });
    }

    const isFetching = loading && !errored;

    if (isFetching) {
      return (
        <LoadingMessage>
          Fetching the poster from Pauling, this should not take too long (in
          theory).
        </LoadingMessage>
      );
    }

    return (
      <FlatList
        data={posters}
        keyExtractor={item => item.id}
        renderItem={this.renderItem}
        ListEmptyComponent={Empty}
      />
    );
  }
}

export default PosterCardList;
<commit_msg>Better renderItem() method signature in PosterCardList<commit_after>/* @flow */
import React from 'react';
import { FlatList } from 'react-native';
import { Toast } from 'native-base';

import LoadingMessage from 'app/LoadingMessage';
import PosterCard from 'app/PosterCard';
import Empty from './Empty';
import type { Poster } from 'app/types';
import type { State as NavigationState } from 'app/reducers/navigation';

type Props = {|
  errored: boolean,
  loading: boolean,
  navigation: NavigationState,
  posters: Array<Poster>,
  toastComponent: typeof Toast,
|};

class PosterCardList extends React.Component {
  props: Props;

  static defaultProps = {
    toastComponent: Toast,
  };

  renderItem = ({ item }: { item: Poster }) => {
    return <PosterCard poster={item} navigation={this.props.navigation} />;
  };

  render() {
    const { errored, loading, posters, toastComponent } = this.props;

    if (errored) {
      toastComponent.show({
        text: 'Failed to load the poster. Please try again later.',
        position: 'bottom',
        buttonText: 'Dismiss',
      });
    }

    const isFetching = loading && !errored;

    if (isFetching) {
      return (
        <LoadingMessage>
          Fetching the poster from Pauling, this should not take too long (in
          theory).
        </LoadingMessage>
      );
    }

    return (
      <FlatList
        data={posters}
        keyExtractor={item => item.id}
        renderItem={this.renderItem}
        ListEmptyComponent={Empty}
      />
    );
  }
}

export default PosterCardList;
<|endoftext|>


<commit_before>export var transforms = [
  {
    "type": "Aggregate",
    "metadata": {"generates": true, "changes": true},
    "params": [
      { "name": "groupby", "type": "field", "array": true, "required": true },
      { "name": "fields", "type": "field", "array": true },
      { "name": "ops", "type": "enum", "array": true,
        "values": [
          "count", "valid", "missing", "distinct",
          "sum", "mean", "average", "variance", "variancep", "stdev",
          "stdevp", "median", "q1", "q3", "modeskew", "min", "max",
          "argmin", "argmax" ] },
      { "name": "as", "type": "string", "array": true },
      { "name": "drop", "type": "boolean", "default": true }
    ],
    "output": ["text", "count"]
  },
  {
    "type": "CountPattern",
    "metadata": {"generates": true, "changes": true},
    "params": [
      { "name": "field", "type": "field", "required": true },
      { "name": "case", "type": "enum", "values": ["upper", "lower", "mixed"], "default": "mixed" },
      { "name": "pattern", "type": "string", "default": "[\\w\"]+" },
      { "name": "stopwords", "type": "string", "default": "" }
    ],
    "output": ["text", "count"]
  },
  {
    "type": "Filter",
    "metadata": {"changes": true},
    "params": [
      { "name": "test", "type": "expr", "required": true }
    ]
  },
  {
    "type": "Fold",
    "metadata": {"generates": true, "changes": true},
    "params": [
      { "name": "fields", "type": "field", "array": true, "required": true }
    ],
    "output": ["key", "value"]
  },
  {
    "name": "formula", "type": "Apply",
    "metadata": {"modifies": true},
    "params": [
      { "name": "apply", "type": "expr", "required": true },
      { "name": "as", "type": "string", "required": true }
    ]
  },
  {
    "type": "Impute",
    "metadata": {"changes": true},
    "params": [
      { "name": "field", "type": "field", "required": true },
      { "name": "groupby", "type": "field", "array": true },
      { "name": "orderby", "type": "field", "array": true },
      { "name": "method", "type": "enum", "default": "value",
        "values": ["value", "mean", "median", "max", "min"] },
      { "name": "value", "default": 0 }
    ]
  },
  {
    "type": "Lookup",
    "metadata": {"modifies": true},
    "params": [
      { "name": "index", "type": "index", "params": [
          {"name": "from", "type": "string", "signal": false, "required": true },
          {"name": "key", "type": "field", "required": true }
        ] },
      { "name": "fields", "type": "field", "array": true, "required": true },
      { "name": "as", "type": "string", "array": true, "required": true },
      { "name": "default", "default": null }
    ]
  },
  {
    "type": "Range",
    "metadata": {"generates": true, "source": true},
    "params": [
      { "name": "start", "type": "number", "required": true },
      { "name": "stop", "type": "number", "required": true },
      { "name": "step", "type": "number", "default": 1 }
    ],
    "output": ["value"]
  },
  {
    "type": "Rank",
    "metadata": {"modifies": true},
    "params": [
      { "name": "field", "type": "field", "required": true },
      { "name": "normalize", "type": "boolean", "default": false }
    ]
  },
  {
    "type": "Sample",
    "metadata": {"source": true, "changes": true},
    "params": [
      { "name": "size", "type": "number", "default": 1000 }
    ]
  },
  {
    "name": "sort", "type": "Collect",
    "metadata": {"source": true},
    "params": [
      { "name": "sort", "type": "compare" }
    ]
  },

  {
    "type": "Force",
    "metadata": {"modifies": true},
    "params": [
      { "name": "static", "type": "boolean", "default": false },
      { "name": "iterations", "type": "number", "default": 300 },
      { "name": "alpha", "type": "number", "default": 1 },
      { "name": "alphaMin", "type": "number", "default": 0.001 },
      { "name": "alphaTarget", "type": "number", "default": 0 },
      { "name": "drag", "type": "number", "default": 0.6 },
      { "name": "fixed", "type": "data" },
      { "name": "forces", "type": "param", "array": true,
        "params": [
          {
            "key": {"force": "center"},
            "params": [
              { "name": "x", "type": "number", "default": 0 },
              { "name": "y", "type": "number", "default": 0 }
            ]
          },
          {
            "key": {"force": "collide"},
            "params": [
              { "name": "radius", "type": "field" },
              { "name": "strength", "type": "number", "default": 0.7 },
              { "name": "iterations", "type": "number", "default": 1 }
            ]
          },
          {
            "key": {"force": "nbody"},
            "params": [
              { "name": "strength", "type": "number", "default": -30 },
              { "name": "theta", "type": "number", "default": 0.9 },
              { "name": "distanceMin", "type": "number", "default": 1 },
              { "name": "distanceMax", "type": "number" }
            ]
          },
          {
            "key": {"force": "link"},
            "params": [
              { "name": "links", "type": "data" },
              { "name": "id", "type": "field" },
              { "name": "distance", "type": "number", "default": 30 },
              { "name": "strength", "type": "number" },
              { "name": "iterations", "type": "number", "default": 1 }
            ]
          },
          {
            "key": {"force": "x"},
            "params": [
              { "name": "strength", "type": "number", "default": 0.1 },
              { "name": "x", "type": "field" }
            ]
          },
          {
            "key": {"force": "y"},
            "params": [
              { "name": "strength", "type": "field", "default": 0.1 },
              { "name": "y", "type": "field" }
            ]
          }
        ] }
    ],
    "output": ["x", "y", "vx", "vy"]
  },
  {
    "type": "LinkPath",
    "metadata": {"modifies": true},
    "params": [
      { "name": "sourceX", "type": "field", "default": "source.x" },
      { "name": "sourceY", "type": "field", "default": "source.y" },
      { "name": "targetX", "type": "field", "default": "target.x" },
      { "name": "targetY", "type": "field", "default": "target.y" },
      { "name": "orient", "type": "enum", "default": "vertical",
        "values": ["horizontal", "vertical", "radial"] },
      { "name": "shape", "type": "enum", "default": "line",
        "values": ["line", "curve", "diagonal", "orthogonal"] }
    ],
    "output": ["path"]
  },
  {
    "type": "Pie",
    "metadata": {"modifies": true},
    "params": [
      { "name": "field", "type": "field" },
      { "name": "startAngle", "type": "number", "default": 0 },
      { "name": "endAngle", "type": "number", "default": 6.283185307179586 },
      { "name": "sort", "type": "boolean", "default": false }
    ],
    "output": ["startAngle", "endAngle"]
  },
  {
    "type": "Stack",
    "metadata": {"modifies": true},
    "params": [
      { "name": "field", "type": "field" },
      { "name": "groupby", "type": "field", "array": true },
      { "name": "sort", "type": "compare" },
      { "name": "offset", "type": "enum", "default": "zero", "values": ["zero", "center", "normalize"] }
    ],
    "output": ["y0", "y1"]
  },
  {
    "type": "Voronoi",
    "metadata": {"modifies": true},
    "params": [
      { "name": "x", "type": "field", "required": true },
      { "name": "y", "type": "field", "required": true },
      { "name": "size", "type": "number", "array": true, "length": 2 }
    ],
    "output": ["path"]
  },

  {
    "type": "Stratify",
    "metadata": {"treesource": true},
    "params": [
      { "name": "key", "type": "field", "required": true },
      { "name": "parentKey", "type": "field", "required": true  }
    ]
  },
  {
    "type": "Nest",
    "metadata": {"treesource": true},
    "params": [
      { "name": "keys", "type": "field", "array": true }
    ]
  },
  {
    "type": "Pack",
    "metadata": {"tree": true, "modifies": true},
    "params": [
      { "name": "padding", "type": "number", "default": 0 },
      { "name": "radius", "type": "field", "default": null },
      { "name": "size", "type": "number", "array": true, "length": 2 }
    ],
    "output": ["x", "y", "r"]
  },
  {
    "type": "Partition",
    "metadata": {"tree": true, "modifies": true},
    "params": [
      { "name": "padding", "type": "number", "default": 0 },
      { "name": "round", "type": "boolean", "default": false },
      { "name": "size", "type": "number", "array": true, "length": 2 }
    ],
    "output": ["x0", "y0", "x1", "y1"]
  },
  {
    "type": "Tree",
    "metadata": {"tree": true, "modifies": true},
    "params": [
      { "name": "method", "type": "enum", "default": "tidy", "values": ["tidy", "cluster"] },
      { "name": "size", "type": "number", "array": true, "length": 2 },
      { "name": "nodeSize", "type": "number", "array": true, "length": 2 }
    ],
    "output": ["x", "y"]
  },
  {
    "type": "Treemap",
    "metadata": {"tree": true, "modifies": true},
    "params": [
      { "name": "method", "type": "enum", "default": "squarify",
        "values": ["squarify", "resquarify", "binary", "dice", "slice", "slicedice"] },
      { "name": "padding", "type": "number", "default": 0 },
      { "name": "paddingInner", "type": "number", "default": 0 },
      { "name": "paddingOuter", "type": "number", "default": 0 },
      { "name": "paddingTop", "type": "number", "default": 0 },
      { "name": "paddingRight", "type": "number", "default": 0 },
      { "name": "paddingBottom", "type": "number", "default": 0 },
      { "name": "paddingLeft", "type": "number", "default": 0 },
      { "name": "ratio", "type": "number", "default": 1.618033988749895 },
      { "name": "round", "type": "boolean", "default": false },
      { "name": "size", "type": "number", "array": true, "length": 2 }
    ],
    "output": ["x0", "y0", "x1", "y1"]
  }
];

var lookup = transforms.reduce(function(map, t) {
  var key = t.name || t.type.toLowerCase();
  return map[key] = t, map;
}, {});

export function transformDef(type) {
  return lookup.hasOwnProperty(type) ? lookup[type] : null;
}
<commit_msg>Add geo transforms.<commit_after>export var transforms = [
  {
    "type": "Aggregate",
    "metadata": {"generates": true, "changes": true},
    "params": [
      { "name": "groupby", "type": "field", "array": true, "required": true },
      { "name": "fields", "type": "field", "array": true },
      { "name": "ops", "type": "enum", "array": true,
        "values": [
          "count", "valid", "missing", "distinct",
          "sum", "mean", "average", "variance", "variancep", "stdev",
          "stdevp", "median", "q1", "q3", "modeskew", "min", "max",
          "argmin", "argmax" ] },
      { "name": "as", "type": "string", "array": true },
      { "name": "drop", "type": "boolean", "default": true }
    ],
    "output": ["text", "count"]
  },
  {
    "type": "CountPattern",
    "metadata": {"generates": true, "changes": true},
    "params": [
      { "name": "field", "type": "field", "required": true },
      { "name": "case", "type": "enum", "values": ["upper", "lower", "mixed"], "default": "mixed" },
      { "name": "pattern", "type": "string", "default": "[\\w\"]+" },
      { "name": "stopwords", "type": "string", "default": "" }
    ],
    "output": ["text", "count"]
  },
  {
    "type": "Filter",
    "metadata": {"changes": true},
    "params": [
      { "name": "test", "type": "expr", "required": true }
    ]
  },
  {
    "type": "Fold",
    "metadata": {"generates": true, "changes": true},
    "params": [
      { "name": "fields", "type": "field", "array": true, "required": true }
    ],
    "output": ["key", "value"]
  },
  {
    "name": "formula", "type": "Apply",
    "metadata": {"modifies": true},
    "params": [
      { "name": "apply", "type": "expr", "required": true },
      { "name": "as", "type": "string", "required": true }
    ]
  },
  {
    "type": "Impute",
    "metadata": {"changes": true},
    "params": [
      { "name": "field", "type": "field", "required": true },
      { "name": "groupby", "type": "field", "array": true },
      { "name": "orderby", "type": "field", "array": true },
      { "name": "method", "type": "enum", "default": "value",
        "values": ["value", "mean", "median", "max", "min"] },
      { "name": "value", "default": 0 }
    ]
  },
  {
    "type": "Lookup",
    "metadata": {"modifies": true},
    "params": [
      { "name": "index", "type": "index", "params": [
          {"name": "from", "type": "string", "signal": false, "required": true },
          {"name": "key", "type": "field", "required": true }
        ] },
      { "name": "fields", "type": "field", "array": true, "required": true },
      { "name": "as", "type": "string", "array": true, "required": true },
      { "name": "default", "default": null }
    ]
  },
  {
    "type": "Range",
    "metadata": {"generates": true, "source": true},
    "params": [
      { "name": "start", "type": "number", "required": true },
      { "name": "stop", "type": "number", "required": true },
      { "name": "step", "type": "number", "default": 1 }
    ],
    "output": ["value"]
  },
  {
    "type": "Rank",
    "metadata": {"modifies": true},
    "params": [
      { "name": "field", "type": "field", "required": true },
      { "name": "normalize", "type": "boolean", "default": false }
    ]
  },
  {
    "type": "Sample",
    "metadata": {"source": true, "changes": true},
    "params": [
      { "name": "size", "type": "number", "default": 1000 }
    ]
  },
  {
    "name": "sort", "type": "Collect",
    "metadata": {"source": true},
    "params": [
      { "name": "sort", "type": "compare" }
    ]
  },

  {
    "type": "Force",
    "metadata": {"modifies": true},
    "params": [
      { "name": "static", "type": "boolean", "default": false },
      { "name": "iterations", "type": "number", "default": 300 },
      { "name": "alpha", "type": "number", "default": 1 },
      { "name": "alphaMin", "type": "number", "default": 0.001 },
      { "name": "alphaTarget", "type": "number", "default": 0 },
      { "name": "drag", "type": "number", "default": 0.6 },
      { "name": "fixed", "type": "data" },
      { "name": "forces", "type": "param", "array": true,
        "params": [
          {
            "key": {"force": "center"},
            "params": [
              { "name": "x", "type": "number", "default": 0 },
              { "name": "y", "type": "number", "default": 0 }
            ]
          },
          {
            "key": {"force": "collide"},
            "params": [
              { "name": "radius", "type": "field" },
              { "name": "strength", "type": "number", "default": 0.7 },
              { "name": "iterations", "type": "number", "default": 1 }
            ]
          },
          {
            "key": {"force": "nbody"},
            "params": [
              { "name": "strength", "type": "number", "default": -30 },
              { "name": "theta", "type": "number", "default": 0.9 },
              { "name": "distanceMin", "type": "number", "default": 1 },
              { "name": "distanceMax", "type": "number" }
            ]
          },
          {
            "key": {"force": "link"},
            "params": [
              { "name": "links", "type": "data" },
              { "name": "id", "type": "field" },
              { "name": "distance", "type": "number", "default": 30 },
              { "name": "strength", "type": "number" },
              { "name": "iterations", "type": "number", "default": 1 }
            ]
          },
          {
            "key": {"force": "x"},
            "params": [
              { "name": "strength", "type": "number", "default": 0.1 },
              { "name": "x", "type": "field" }
            ]
          },
          {
            "key": {"force": "y"},
            "params": [
              { "name": "strength", "type": "field", "default": 0.1 },
              { "name": "y", "type": "field" }
            ]
          }
        ] }
    ],
    "output": ["x", "y", "vx", "vy"]
  },
  {
    "type": "LinkPath",
    "metadata": {"modifies": true},
    "params": [
      { "name": "sourceX", "type": "field", "default": "source.x" },
      { "name": "sourceY", "type": "field", "default": "source.y" },
      { "name": "targetX", "type": "field", "default": "target.x" },
      { "name": "targetY", "type": "field", "default": "target.y" },
      { "name": "orient", "type": "enum", "default": "vertical",
        "values": ["horizontal", "vertical", "radial"] },
      { "name": "shape", "type": "enum", "default": "line",
        "values": ["line", "curve", "diagonal", "orthogonal"] }
    ],
    "output": ["path"]
  },
  {
    "type": "Pie",
    "metadata": {"modifies": true},
    "params": [
      { "name": "field", "type": "field" },
      { "name": "startAngle", "type": "number", "default": 0 },
      { "name": "endAngle", "type": "number", "default": 6.283185307179586 },
      { "name": "sort", "type": "boolean", "default": false }
    ],
    "output": ["startAngle", "endAngle"]
  },
  {
    "type": "Stack",
    "metadata": {"modifies": true},
    "params": [
      { "name": "field", "type": "field" },
      { "name": "groupby", "type": "field", "array": true },
      { "name": "sort", "type": "compare" },
      { "name": "offset", "type": "enum", "default": "zero", "values": ["zero", "center", "normalize"] }
    ],
    "output": ["y0", "y1"]
  },
  {
    "type": "Voronoi",
    "metadata": {"modifies": true},
    "params": [
      { "name": "x", "type": "field", "required": true },
      { "name": "y", "type": "field", "required": true },
      { "name": "size", "type": "number", "array": true, "length": 2 },
      { "name": "extent", "type": "array", "array": true, "length": 2,
        "content": {"type": "number", "array": true, "length": 2} }
    ],
    "output": ["path"]
  },

  {
    "type": "GeoPath",
    "metadata": {"modifies": true},
    "params": [
      { "name": "projection", "type": "projection", "required": true },
      { "name": "pointRadius", "type": "number" },
      { "name": "field", "type": "field" },
      { "name": "as", "type": "string" }
    ],
    "output": ["path"]
  },
  {
    "type": "GeoPoint",
    "metadata": {"modifies": true},
    "params": [
      { "name": "projection", "type": "projection", "required": true },
      { "name": "fields", "type": "field", "array": true, "required": true, "length": 2 },
      { "name": "as", "type": "string", "array": true, "length": 2 }
    ],
    "output": ["x", "y"]
  },
  {
    "type": "Graticule",
    "metadata": {"generates": true, "changes": true},
    "params": [
      { "name": "extent", "type": "array", "array": true, "length": 2,
        "content": {"type": "number", "array": true, "length": 2} },
      { "name": "extentMajor", "type": "array", "array": true, "length": 2,
        "content": {"type": "number", "array": true, "length": 2} },
      { "name": "extentMinor", "type": "array", "array": true, "length": 2,
        "content": {"type": "number", "array": true, "length": 2} },
      { "name": "step", "type": "number", "array": true, "length": 2 },
      { "name": "stepMajor", "type": "number", "array": true, "length": 2, "default": [90, 360] },
      { "name": "stepMinor", "type": "number", "array": true, "length": 2, "default": [10, 10] },
      { "name": "precision", "type": "number", "default": 2.5 }
    ]
  },

  {
    "type": "Stratify",
    "metadata": {"treesource": true},
    "params": [
      { "name": "key", "type": "field", "required": true },
      { "name": "parentKey", "type": "field", "required": true  }
    ]
  },
  {
    "type": "Nest",
    "metadata": {"treesource": true},
    "params": [
      { "name": "keys", "type": "field", "array": true }
    ]
  },
  {
    "type": "Pack",
    "metadata": {"tree": true, "modifies": true},
    "params": [
      { "name": "padding", "type": "number", "default": 0 },
      { "name": "radius", "type": "field", "default": null },
      { "name": "size", "type": "number", "array": true, "length": 2 }
    ],
    "output": ["x", "y", "r"]
  },
  {
    "type": "Partition",
    "metadata": {"tree": true, "modifies": true},
    "params": [
      { "name": "padding", "type": "number", "default": 0 },
      { "name": "round", "type": "boolean", "default": false },
      { "name": "size", "type": "number", "array": true, "length": 2 }
    ],
    "output": ["x0", "y0", "x1", "y1"]
  },
  {
    "type": "Tree",
    "metadata": {"tree": true, "modifies": true},
    "params": [
      { "name": "method", "type": "enum", "default": "tidy", "values": ["tidy", "cluster"] },
      { "name": "size", "type": "number", "array": true, "length": 2 },
      { "name": "nodeSize", "type": "number", "array": true, "length": 2 }
    ],
    "output": ["x", "y"]
  },
  {
    "type": "Treemap",
    "metadata": {"tree": true, "modifies": true},
    "params": [
      { "name": "method", "type": "enum", "default": "squarify",
        "values": ["squarify", "resquarify", "binary", "dice", "slice", "slicedice"] },
      { "name": "padding", "type": "number", "default": 0 },
      { "name": "paddingInner", "type": "number", "default": 0 },
      { "name": "paddingOuter", "type": "number", "default": 0 },
      { "name": "paddingTop", "type": "number", "default": 0 },
      { "name": "paddingRight", "type": "number", "default": 0 },
      { "name": "paddingBottom", "type": "number", "default": 0 },
      { "name": "paddingLeft", "type": "number", "default": 0 },
      { "name": "ratio", "type": "number", "default": 1.618033988749895 },
      { "name": "round", "type": "boolean", "default": false },
      { "name": "size", "type": "number", "array": true, "length": 2 }
    ],
    "output": ["x0", "y0", "x1", "y1"]
  }
];

var lookup = transforms.reduce(function(map, t) {
  var key = t.name || t.type.toLowerCase();
  return map[key] = t, map;
}, {});

export function transformDef(type) {
  return lookup.hasOwnProperty(type) ? lookup[type] : null;
}
<|endoftext|>