<commit_before>a:/**
 * @license
 * Copyright 2012 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

CLASS({
  name: 'Protobuf',
  package: 'foam.grammars',

  properties: [
    {
      name: 'parser',
      factory: function() {
        var X = this.X;
        return {
          __proto__: grammar,

          START: sym('proto'),

          d: range('0', '9'),

          w: alt(sym('d'), range('a', 'z'), range('A', 'Z'), "_"),

          a: alt(range('a', 'z'), range('A', 'Z')),

          proto: repeat(alt(
            sym('message'),
            sym('extend'),
            sym('enum'),
            sym('import'),
            sym('package'),
            sym('option'),
            sym('syntax'), ';')),

          syntax: seq("syntax", "=", sym('strLit'), ";"),

          import: seq("import", sym('strLit'), ";"),

          package: seq("package", sym('ident'), ";"),

          option: seq("option", sym('optionBody'), ";"),

          optionBody: seq(sym('ident'), repeat(seq(".", sym('ident'))), "=", sym('constant')),

          message: seq("message", sym('ident'), sym('messageBody')),

          extend: seq("extend", sym('userType'), sym('messageBody')),

          enum: seq("enum", sym('ident'), "{", repeat(alt(sym('option'), sym('enumField'), ";")), "}"),

          enumField: seq(sym('ident'), "=", sym('sintLit'), ";"),

          service: seq("service", sym('ident'), "{", repeat(seq(sym('option'), sym('rpc')), ";"), "}"),

          rpc: seq("rpc", sym('ident'), "(", sym('userType'), ")", "returns", "(", sym('userType'), ")", ";"),

          messageBody: seq(
            "{",
            repeat(
              alt(sym('field'), sym('enum'), sym('message'), sym('extend'), sym('extensions'), sym('group'), sym('option'), ';')
            ),
            "}"),

          group: seq(sym('modifier'), "group", sym('camelIdent'), "=", sym('intLit'), sym('messageBody')),

          // tag number must be 2^28-1 or lower
          field: seq(
            sym('modifier'),
            sym('type'),
            sym('ident'),
            "=",
            sym('intLit'),
            optional(seq("[", sym('fieldOption'), repeat(",", sym('fieldOption') ), "]")),
            ";"),

          fieldOption: alt(sym('optionBody'), seq("default", "=", sym('constant'))),

          extensions: seq("extensions", sym('intLit'), "to", alt(sym('intLit'), "max"), ";"),

          modifier: alt("required", "optional", "repeated"),

          type: alt(
            "double", "float", "int32", "int64", "uint32", "uint64",
            "sint32", "sint64", "fixed32", "fixed64", "sfixed32",
            "sfixed64", "bool", "string", "bytes", sym('userType')),

          // leading dot for identifiers means they're fully qualified
          userType: noskip(plus(seq(optional("."), sym('ident')))),

          constant: alt(sym('ident'), sym('sintLit'), sym('floatLit'), sym('strLit'), sym('boolLit')),

          ident: seq(sym('a'), repeat(sym('w'))),

          // according to parser.cc, group names must start with a capital letter as a
          // hack for backwards-compatibility
          camelIdent: seq(range('A', 'Z'), repeat(sym('w'))),

          intLit: alt(sym('decInt'), sym('hexInt'), sym('octInt')),

          sintLit: alt(
            seq(optional(alt('+', '-')), sym('decInt')),
            sym('intLit')),

          decInt: plus(sym('d')),

          hexInt: seq('/0', alt('x', 'X'), plus(alt(range('A','F'), range('a', 'f'), range('0', '9')))),

          octInt: seq('/0', plus(range('0', '7'))),

          floatLit:
          seq(
            seq(
              sym('decInt'),
              optional('.', sym('decInt'))),
            optional(
              seq(
                alt('E', 'e'),
                optional(alt('+', '-')),
                sym('decInt')))),

          boolLit: alt("true", "false"),

          strLit: noskip(seq(sym('quote'), repeat(alt(sym('hexEscape'), sym('octEscape'), sym('charEscape'), sym('quoteEscape'), not(sym('quote'), anyChar))) ,sym('quote'))),

          quote: alt('"', "'"),

          hexEscape: seq('\\', alt('x', 'X'), repeat(alt(range('A','F'), range('a', 'f'), range('0', '9'), undefined, 1,2))),

          octEscape: seq('\\0', repeat(range('0', '7'), undefined, 1, 3)),

          charEscape: seq('\\', alt('a', 'b', 'f', 'n', 'r', 't', 'v','?')),

          quoteEscape: seq('\\"'),

        }.addActions({
          package: function(a) {
            this.currentPackage = a[1];
          },

          quoteEscape: function(a) {
            return ['"'];
          },

          enumField: function(a) {
            return [a[0], a[2]];
          },

          enum: function(a) {
            var constants = {};
            var name = a[1];
            var values = a[3];
            for ( var i = 0 ; i < values.length ; i++ ) {
              var value = values[i];
              constants[value[0]] = value[1][1];
            }
            var m = Model.create({
              name: name,
              package: this.currentPackage,
              constants: constants
            });
            return m;
          },

          userType: function(a) {
            return a[0].join('');
          },

          field: function(a) {
            if (a[0] === 'repeated') {
              return ArrayProperty.create({
                subType: a[1],
                name: a[2],
                prototag: a[4]
              });
            } else {
              var prop = Property.create({
                type: a[1],
                name: a[2],
                prototag: a[4],
                required: a[0] === 'required'
              });
              // TODO: Hack for enums unti they're modelled.
              var subtype = (this.ctx || GLOBAL)[prop.type];
              if ( subtype && subtype.type === 'Enum' ) {
                prop.outProtobuf = function(obj, out) {
                  if ( this.f(obj) === "" ) return;
                  outProtobufPrimitive('int32', this.prototag, this.f(obj), out);
                };
              }
              return prop;
            }
          },

          message: function(a) {
            var properties = [];
            var models = [];
            for (var i = 0; i < a[2].length; i++) {
              if ( ! a[2][i] ) continue;

              if ( Property.isInstance(a[2][i]) ) {
                properties.push(a[2][i]);
              } else if ( Model.isInstance(a[2][i]) ) {
                models.push(a[2][i]);
              }
            }
            var args = {
              name: a[1],
              properties: properties,
              models: models
            };
            if ( this.currentPackage ) args.package = this.currentPackage;
            var model = Model.create(args);
            return model;
          },

          messageBody: function(a) { return a[1]; },

          ident: function(a) { return a[0] + a[1].join(''); },

          decInt: function(a) { return parseInt(a.join('')); }
        });
      }
    }
  ]
});
<commit_msg>Split protobuf parser into grammar and model generator so it can be customized for other uses.<commit_after>/**
 * @license
 * Copyright 2012 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

CLASS({
  name: 'Protobuf',
  package: 'foam.grammars',

  properties: [
    {
      name: 'grammar',
      factory: function() {
        return {
          __proto__: grammar,

          START: sym('proto'),

          d: range('0', '9'),

          w: alt(sym('d'), range('a', 'z'), range('A', 'Z'), "_"),

          a: alt(range('a', 'z'), range('A', 'Z')),

          proto: repeat(alt(
            sym('message'),
            sym('extend'),
            sym('enum'),
            sym('import'),
            sym('package'),
            sym('option'),
            sym('syntax'), ';')),

          syntax: seq("syntax", "=", sym('strLit'), ";"),

          import: seq("import", sym('strLit'), ";"),

          package: seq("package", sym('ident'), ";"),

          option: seq("option", sym('optionBody'), ";"),

          optionBody: seq(sym('ident'), repeat(seq(".", sym('ident'))), "=", sym('constant')),

          message: seq("message", sym('ident'), sym('messageBody')),

          extend: seq("extend", sym('userType'), sym('messageBody')),

          enum: seq("enum", sym('ident'), "{", repeat(alt(sym('option'), sym('enumField'), ";")), "}"),

          enumField: seq(sym('ident'), "=", sym('sintLit'), ";"),

          service: seq("service", sym('ident'), "{", repeat(seq(sym('option'), sym('rpc')), ";"), "}"),

          rpc: seq("rpc", sym('ident'), "(", sym('userType'), ")", "returns", "(", sym('userType'), ")", ";"),

          messageBody: seq(
            "{",
            repeat(
              alt(sym('field'), sym('enum'), sym('message'), sym('extend'), sym('extensions'), sym('group'), sym('option'), ';')
            ),
            "}"),

          group: seq(sym('modifier'), "group", sym('camelIdent'), "=", sym('intLit'), sym('messageBody')),

          // tag number must be 2^28-1 or lower
          field: seq(
            sym('modifier'),
            sym('type'),
            sym('ident'),
            "=",
            sym('intLit'),
            optional(seq("[", sym('fieldOption'), repeat(",", sym('fieldOption') ), "]")),
            ";"),

          fieldOption: alt(sym('optionBody'), seq("default", "=", sym('constant'))),

          extensions: seq("extensions", sym('intLit'), "to", alt(sym('intLit'), "max"), ";"),

          modifier: alt("required", "optional", "repeated"),

          type: alt(
            "double", "float", "int32", "int64", "uint32", "uint64",
            "sint32", "sint64", "fixed32", "fixed64", "sfixed32",
            "sfixed64", "bool", "string", "bytes", sym('userType')),

          // leading dot for identifiers means they're fully qualified
          userType: noskip(plus(seq(optional("."), sym('ident')))),

          constant: alt(sym('ident'), sym('sintLit'), sym('floatLit'), sym('strLit'), sym('boolLit')),

          ident: seq(sym('a'), repeat(sym('w'))),

          // according to parser.cc, group names must start with a capital letter as a
          // hack for backwards-compatibility
          camelIdent: seq(range('A', 'Z'), repeat(sym('w'))),

          intLit: alt(sym('decInt'), sym('hexInt'), sym('octInt')),

          sintLit: alt(
            seq(optional(alt('+', '-')), sym('decInt')),
            sym('intLit')),

          decInt: plus(sym('d')),

          hexInt: seq('/0', alt('x', 'X'), plus(alt(range('A','F'), range('a', 'f'), range('0', '9')))),

          octInt: seq('/0', plus(range('0', '7'))),

          floatLit:
          seq(
            seq(
              sym('decInt'),
              optional('.', sym('decInt'))),
            optional(
              seq(
                alt('E', 'e'),
                optional(alt('+', '-')),
                sym('decInt')))),

          boolLit: alt("true", "false"),

          strLit: noskip(seq(sym('quote'), repeat(alt(sym('hexEscape'), sym('octEscape'), sym('charEscape'), sym('quoteEscape'), not(sym('quote'), anyChar))) ,sym('quote'))),

          quote: alt('"', "'"),

          hexEscape: seq('\\', alt('x', 'X'), repeat(alt(range('A','F'), range('a', 'f'), range('0', '9'), undefined, 1,2))),

          octEscape: seq('\\0', repeat(range('0', '7'), undefined, 1, 3)),

          charEscape: seq('\\', alt('a', 'b', 'f', 'n', 'r', 't', 'v','?')),

          quoteEscape: seq('\\"'),
        }
      }
    },
    {
      name: 'parser',
      factory: function() {
        var X = this.X;
        return this.grammar.addActions({
          package: function(a) {
            this.currentPackage = a[1];
          },

          quoteEscape: function(a) {
            return ['"'];
          },

          enumField: function(a) {
            return [a[0], a[2]];
          },

          enum: function(a) {
            var constants = {};
            var name = a[1];
            var values = a[3];
            for ( var i = 0 ; i < values.length ; i++ ) {
              var value = values[i];
              constants[value[0]] = value[1][1];
            }
            var m = Model.create({
              name: name,
              package: this.currentPackage,
              constants: constants
            });
            return m;
          },

          userType: function(a) {
            return a[0].join('');
          },

          field: function(a) {
            if (a[0] === 'repeated') {
              return ArrayProperty.create({
                subType: a[1],
                name: a[2],
                prototag: a[4]
              });
            } else {
              var prop = Property.create({
                type: a[1],
                name: a[2],
                prototag: a[4],
                required: a[0] === 'required'
              });
              // TODO: Hack for enums unti they're modelled.
              var subtype = (this.ctx || GLOBAL)[prop.type];
              if ( subtype && subtype.type === 'Enum' ) {
                prop.outProtobuf = function(obj, out) {
                  if ( this.f(obj) === "" ) return;
                  outProtobufPrimitive('int32', this.prototag, this.f(obj), out);
                };
              }
              return prop;
            }
          },

          message: function(a) {
            var properties = [];
            var models = [];
            for (var i = 0; i < a[2].length; i++) {
              if ( ! a[2][i] ) continue;

              if ( Property.isInstance(a[2][i]) ) {
                properties.push(a[2][i]);
              } else if ( Model.isInstance(a[2][i]) ) {
                models.push(a[2][i]);
              }
            }
            var args = {
              name: a[1],
              properties: properties,
              models: models
            };
            if ( this.currentPackage ) args.package = this.currentPackage;
            var model = Model.create(args);
            return model;
          },

          messageBody: function(a) { return a[1]; },

          ident: function(a) { return a[0] + a[1].join(''); },

          decInt: function(a) { return parseInt(a.join('')); }
        });
      }
    }
  ]
});
<|endoftext|>


<commit_before>// Copyright 2015, University of Colorado Boulder

/**
 * Deploy a simulation by sending a request to our build-server.
 *
 * @author Aaron Davis
 */
/* eslint-env node */
'use strict';

// modules
var deployUtil = require( '../../../chipper/js/grunt/deployUtil' );
var getDeployConfig = require( '../../../chipper/js/common/getDeployConfig' );
var querystring = require( 'querystring' );
var request = require( 'request' );

/**
 * @param grunt - the grunt instance
 * @param {Object} buildConfig
 * @param {function} callback - optional callback to run when finished, defaults to grunt.task.current.async()
 */
module.exports = function( grunt, buildConfig, callback ) {

  // Get the branch name to cross check brand/branch name for PhET-iO
  deployUtil.exec( grunt, 'git rev-parse --abbrev-ref HEAD', function( err, stdout ) {
    if ( err ) {
      grunt.fail.warn( 'Error getting branch name: ', err );
    }

    var isPhetioBranch = stdout.indexOf( '-phetio' ) >= 0;
    var isPhetioBrand = buildConfig.brand === 'phet-io';

    // If XOR on the two phetio settings, then throw an error.
    // We only want phet deploys from non phetio branches (i.e. 1.3 instead of 1.3-phetio).
    // We only want phet-io deploys from phetio branches.
    // See see https://github.com/phetsims/chipper/issues/582
    if ( isPhetioBranch ^ isPhetioBrand ) {
      grunt.fail.warn( 'Either in phet-io brand, or on a phet-io branch, but not both.' );
    }

    // configuration info from external files
    var deployConfig = getDeployConfig( grunt, global.phet.chipper.fs );

    // read dependencies.json (required)
    var dependenciesJSON = grunt.file.readJSON( 'build/dependencies.json' );

    var json = {
      repos: JSON.stringify( dependenciesJSON ),
      locales: grunt.option( 'locales' ) || '*',
      simName: deployConfig.name,
      version: deployConfig.version,
      authorizationCode: deployConfig.buildServerAuthorizationCode
    };

    if ( grunt.option( 'option' ) === 'rc' ) {
      json.option = 'rc';
    }

    if ( grunt.option( 'email' ) ) {
      json.email = grunt.option( 'email' );
    }
    else if ( deployConfig.buildServerNotifyEmail ) {
      json.email = deployConfig.buildServerNotifyEmail;
    }

    var productionServerURL = deployConfig.productionServerURL;
    var url = productionServerURL + '/deploy-html-simulation';

    var done = callback || grunt.task.current.async();

    if ( grunt.option( 'dryRun' ) ) {
      grunt.log.writeln( 'Option \'dryRun\' set, request will not be sent to build server.' );
      grunt.log.writeln( 'URL = ' + url );
      for ( var key in json ) {
        if ( json.hasOwnProperty( key ) ) {
          grunt.log.writeln( key + ':' + json[ key ] );
        }
      }
      done();
    }
    else {

      // send the build request to the build server
      request.post( { url, json }, function( error, response, body ) {
        if ( error ) {
          grunt.fail.warn( 'Build request failed with error ' + error + '.' );
        }
        else if ( response.statusCode !== 200 ) {
          grunt.fail.warn( 'Build request failed with status code ' + response.statusCode + '.' );
        }
        else {
          grunt.log.writeln( 'Build request sent successfully, URL: ' + url );
        }
        done();
      } );
    }
  } );

};
<commit_msg>Reverted es6 code to es5<commit_after>// Copyright 2015, University of Colorado Boulder

/**
 * Deploy a simulation by sending a request to our build-server.
 *
 * @author Aaron Davis
 */
/* eslint-env node */
'use strict';

// modules
var deployUtil = require( '../../../chipper/js/grunt/deployUtil' );
var getDeployConfig = require( '../../../chipper/js/common/getDeployConfig' );
var querystring = require( 'querystring' );
var request = require( 'request' );

/**
 * @param grunt - the grunt instance
 * @param {Object} buildConfig
 * @param {function} callback - optional callback to run when finished, defaults to grunt.task.current.async()
 */
module.exports = function( grunt, buildConfig, callback ) {

  // Get the branch name to cross check brand/branch name for PhET-iO
  deployUtil.exec( grunt, 'git rev-parse --abbrev-ref HEAD', function( err, stdout ) {
    if ( err ) {
      grunt.fail.warn( 'Error getting branch name: ', err );
    }

    var isPhetioBranch = stdout.indexOf( '-phetio' ) >= 0;
    var isPhetioBrand = buildConfig.brand === 'phet-io';

    // If XOR on the two phetio settings, then throw an error.
    // We only want phet deploys from non phetio branches (i.e. 1.3 instead of 1.3-phetio).
    // We only want phet-io deploys from phetio branches.
    // See see https://github.com/phetsims/chipper/issues/582
    if ( isPhetioBranch ^ isPhetioBrand ) {
      grunt.fail.warn( 'Either in phet-io brand, or on a phet-io branch, but not both.' );
    }

    // configuration info from external files
    var deployConfig = getDeployConfig( grunt, global.phet.chipper.fs );

    // read dependencies.json (required)
    var dependenciesJSON = grunt.file.readJSON( 'build/dependencies.json' );

    var json = {
      repos: JSON.stringify( dependenciesJSON ),
      locales: grunt.option( 'locales' ) || '*',
      simName: deployConfig.name,
      version: deployConfig.version,
      authorizationCode: deployConfig.buildServerAuthorizationCode
    };

    if ( grunt.option( 'option' ) === 'rc' ) {
      json.option = 'rc';
    }

    if ( grunt.option( 'email' ) ) {
      json.email = grunt.option( 'email' );
    }
    else if ( deployConfig.buildServerNotifyEmail ) {
      json.email = deployConfig.buildServerNotifyEmail;
    }

    var productionServerURL = deployConfig.productionServerURL;
    var url = productionServerURL + '/deploy-html-simulation';

    var done = callback || grunt.task.current.async();

    if ( grunt.option( 'dryRun' ) ) {
      grunt.log.writeln( 'Option \'dryRun\' set, request will not be sent to build server.' );
      grunt.log.writeln( 'URL = ' + url );
      for ( var key in json ) {
        if ( json.hasOwnProperty( key ) ) {
          grunt.log.writeln( key + ':' + json[ key ] );
        }
      }
      done();
    }
    else {

      // send the build request to the build server
      request.post( { url: url, json: json }, function( error, response, body ) {
        if ( error ) {
          grunt.fail.warn( 'Build request failed with error ' + error + '.' );
        }
        else if ( response.statusCode !== 200 ) {
          grunt.fail.warn( 'Build request failed with status code ' + response.statusCode + '.' );
        }
        else {
          grunt.log.writeln( 'Build request sent successfully, URL: ' + url );
        }
        done();
      } );
    }
  } );

};
<|endoftext|>


<commit_before>// Copyright 2015, University of Colorado Boulder

/**
 * Deploy a simulation by sending a request to our build-server.
 *
 * @author Aaron Davis
 */
/* eslint-env node */
'use strict';

// modules
var deployUtil = require( '../../../chipper/js/grunt/deployUtil' );
var getDeployConfig = require( '../../../chipper/js/common/getDeployConfig' );
var querystring = require( 'querystring' );
var request = require( 'request' );

/**
 * @param grunt - the grunt instance
 * @param {Object} buildConfig
 * @param {function} callback - optional callback to run when finished, defaults to grunt.task.current.async()
 */
module.exports = function( grunt, buildConfig, callback ) {

  // Get the branch name to cross check brand/branch name for PhET-iO
  deployUtil.exec( grunt, 'git rev-parse --abbrev-ref HEAD', function( err, stdout ) {
    if ( err ) {
      grunt.fail.warn( 'Error getting branch name: ', err );
    }

    var isPhetioBranch = stdout.indexOf( '-phetio' ) >= 0;
    var isPhetioBrand = buildConfig.brand === 'phet-io';

    // If XOR on the two phetio settings, then throw an error.
    // We only want phet deploys from non phetio branches (i.e. 1.3 instead of 1.3-phetio).
    // We only want phet-io deploys from phetio branches.
    // See see https://github.com/phetsims/chipper/issues/582
    if ( isPhetioBranch ^ isPhetioBrand ) {
      grunt.fail.warn( 'Either in phet-io brand, or on a phet-io branch, but not both.' );
    }

    // configuration info from external files
    var deployConfig = getDeployConfig( grunt, global.phet.chipper.fs );

    // read dependencies.json (required)
    var dependenciesJSON = grunt.file.readJSON( 'build/dependencies.json' );

    var json = {
      repos: JSON.stringify( dependenciesJSON ),
      locales: grunt.option( 'locales' ) || '*',
      simName: deployConfig.name,
      version: deployConfig.version,
      authorizationCode: deployConfig.buildServerAuthorizationCode
    };

    if ( grunt.option( 'option' ) === 'rc' ) {
      json.option = 'rc';
    }

    if ( grunt.option( 'email' ) ) {
      json.email = grunt.option( 'email' );
    }
    else if ( deployConfig.buildServerNotifyEmail ) {
      json.email = deployConfig.buildServerNotifyEmail;
    }

    var productionServerURL = deployConfig.productionServerURL;
    var url = productionServerURL + '/deploy-html-simulation';

    var done = callback || grunt.task.current.async();

    if ( grunt.option( 'dryRun' ) ) {
      grunt.log.writeln( 'Option \'dryRun\' set, request will not be sent to build server.' );
      grunt.log.writeln( 'URL = ' + url );
      for ( var key in json ) {
        if ( json.hasOwnProperty( key ) ) {
          grunt.log.writeln( key + ':' + json[ key ] );
        }
      }
      done();
    }
    else {

      // send the build request to the build server
      request.post( { url: url, json: json }, function( error, response, body ) {
        if ( error ) {
          grunt.fail.warn( 'Build request failed with error ' + error + '.' );
        }
        else if ( response.statusCode !== 200 ) {
          grunt.fail.warn( 'Build request failed with status code ' + response.statusCode + '.' );
        }
        else {
          grunt.log.writeln( 'Build request sent successfully, URL: ' + url );
        }
        done();
      } );
    }
  } );

};
<commit_msg>Removed unused require statement<commit_after>// Copyright 2015, University of Colorado Boulder

/**
 * Deploy a simulation by sending a request to our build-server.
 *
 * @author Aaron Davis
 */
/* eslint-env node */
'use strict';

// modules
var deployUtil = require( '../../../chipper/js/grunt/deployUtil' );
var getDeployConfig = require( '../../../chipper/js/common/getDeployConfig' );
var request = require( 'request' );

/**
 * @param grunt - the grunt instance
 * @param {Object} buildConfig
 * @param {function} callback - optional callback to run when finished, defaults to grunt.task.current.async()
 */
module.exports = function( grunt, buildConfig, callback ) {

  // Get the branch name to cross check brand/branch name for PhET-iO
  deployUtil.exec( grunt, 'git rev-parse --abbrev-ref HEAD', function( err, stdout ) {
    if ( err ) {
      grunt.fail.warn( 'Error getting branch name: ', err );
    }

    var isPhetioBranch = stdout.indexOf( '-phetio' ) >= 0;
    var isPhetioBrand = buildConfig.brand === 'phet-io';

    // If XOR on the two phetio settings, then throw an error.
    // We only want phet deploys from non phetio branches (i.e. 1.3 instead of 1.3-phetio).
    // We only want phet-io deploys from phetio branches.
    // See see https://github.com/phetsims/chipper/issues/582
    if ( isPhetioBranch ^ isPhetioBrand ) {
      grunt.fail.warn( 'Either in phet-io brand, or on a phet-io branch, but not both.' );
    }

    // configuration info from external files
    var deployConfig = getDeployConfig( grunt, global.phet.chipper.fs );

    // read dependencies.json (required)
    var dependenciesJSON = grunt.file.readJSON( 'build/dependencies.json' );

    var json = {
      repos: JSON.stringify( dependenciesJSON ),
      locales: grunt.option( 'locales' ) || '*',
      simName: deployConfig.name,
      version: deployConfig.version,
      authorizationCode: deployConfig.buildServerAuthorizationCode
    };

    if ( grunt.option( 'option' ) === 'rc' ) {
      json.option = 'rc';
    }

    if ( grunt.option( 'email' ) ) {
      json.email = grunt.option( 'email' );
    }
    else if ( deployConfig.buildServerNotifyEmail ) {
      json.email = deployConfig.buildServerNotifyEmail;
    }

    var productionServerURL = deployConfig.productionServerURL;
    var url = productionServerURL + '/deploy-html-simulation';

    var done = callback || grunt.task.current.async();

    if ( grunt.option( 'dryRun' ) ) {
      grunt.log.writeln( 'Option \'dryRun\' set, request will not be sent to build server.' );
      grunt.log.writeln( 'URL = ' + url );
      for ( var key in json ) {
        if ( json.hasOwnProperty( key ) ) {
          grunt.log.writeln( key + ':' + json[ key ] );
        }
      }
      done();
    }
    else {

      // send the build request to the build server
      request.post( { url: url, json: json }, function( error, response, body ) {
        if ( error ) {
          grunt.fail.warn( 'Build request failed with error ' + error + '.' );
        }
        else if ( response.statusCode !== 200 ) {
          grunt.fail.warn( 'Build request failed with status code ' + response.statusCode + '.' );
        }
        else {
          grunt.log.writeln( 'Build request sent successfully, URL: ' + url );
        }
        done();
      } );
    }
  } );

};
<|endoftext|>


<commit_before>// Copyright 2015-2020, University of Colorado Boulder

/**
 * Creates a composite report of all of the 3rd party images, code, sounds and other media used by all of the published
 * PhET Simulations. The reports is published at: https://github.com/phetsims/sherpa/blob/master/third-party-licenses.md
 *
 * Usage:
 * grunt report-third-party
 * // then push sherpa/third-party-licenses.md
 *
 * Third party entries are parsed from the HTML files for all simulations published on the PhET website.
 * See getLicenseEntry.js for documentation of the fields in the entries.
 *
 * Copy the local-auth-code key value from phet-server:/etc/tomcat/context.xml into the value for
 * websiteAuthorizationCode in ~/.phet/build-local.json
 *
 * @author Sam Reid
 * @author Jonathan Olson <jonathan.olson@colorado.edu>
 */

'use strict';

const _ = require( 'lodash' ); // eslint-disable-line require-statement-match
const assert = require( 'assert' );
const ChipperConstants = require( '../common/ChipperConstants' );
const fs = require( 'fs' );
const grunt = require( 'grunt' );
const https = require( 'https' );
const rp = require( 'request-promise-native' ); // eslint-disable-line require-statement-match

/**
 * @returns {Promise} - Resolves when complete.
 */
module.exports = async function() {

  // read configuration file - required to write to website database
  const serverName = 'phet.colorado.edu';
  const BUILD_LOCAL_FILENAME = process.env.HOME + '/.phet/build-local.json';
  const buildLocalJSON = JSON.parse( fs.readFileSync( BUILD_LOCAL_FILENAME, { encoding: 'utf-8' } ) );
  assert( buildLocalJSON && buildLocalJSON.websiteAuthorizationCode, 'websiteAuthorizationCode missing from ' + BUILD_LOCAL_FILENAME );

  // The file where the report will be written
  const outputFilename = '../sherpa/third-party-licenses.md';

  // Aggregate results for each of the license types
  const compositeCode = {};
  const compositeMedia = {};

  // List of all of the repository names, so that we can detect which libraries are used by all-sims
  const simTitles = [];

  // List of libraries for each sim
  // Type: string in JSON format
  const simLibraries = [];

  // Download all sims. If it's not published, it will be skipped in the report
  const activeSims = fs.readFileSync( '../perennial/data/active-sims', 'utf-8' ).trim().split( '\n' ).map( sim => sim.trim() );

  for ( const sim of activeSims ) {
    const url = `https://${serverName}/sims/html/${sim}/latest/${sim}_en.html`;
    console.log( `downloading ${sim}` );
    try {
      const html = ( await rp( url ) ).trim();

      const startIndex = html.indexOf( ChipperConstants.START_THIRD_PARTY_LICENSE_ENTRIES );
      const endIndex = html.indexOf( ChipperConstants.END_THIRD_PARTY_LICENSE_ENTRIES );
      const substring = html.substring( startIndex, endIndex );

      const firstCurlyBrace = substring.indexOf( '{' );
      const lastCurlyBrace = substring.lastIndexOf( '}' );
      const jsonString = substring.substring( firstCurlyBrace, lastCurlyBrace + 1 );

      const json = JSON.parse( jsonString );

      let title = parseTitle( html );
      if ( !title || title.indexOf( 'undefined' ) === 0 || title.indexOf( 'TITLE' ) >= 0 ) {
        grunt.log.writeln( `title not found for ${sim}` );
        title = sim;
      }
      augment( title, json.lib, compositeCode );
      augment( title, json.sounds, compositeMedia );
      augment( title, json.images, compositeMedia );

      simTitles.push( title );

      // Concatenate all the libraries for this sim with html newline.
      let libString = '';
      for ( const entry in json.lib ) {
        libString += entry + '
';
      }

      //  Update the object to be pushed to the website database
      simLibraries.push( {
        name: sim,
        libraries: libString
      } );
    }
    catch( e ) {
      console.log( `${sim} not found on production` );
    }
  }

  const requestPromise = new Promise( ( resolve, reject ) => {
    // Change libraryobject to string in format that the database will recognize.
    // i.e. '{"sim-name":"Library Name
Library Name", ...}'
    const libraryString = '{' + simLibraries.map( o => `"${o.name}":"${o.libraries}"` ).join( ',' ) + '}';

    const requestOptions = {
      host: serverName,
      path: '/services/add-simulation-libraries',
      port: 443,
      method: 'POST',
      auth: 'token:' + buildLocalJSON.websiteAuthorizationCode,
      headers: {
        'Content-Type': 'application/x-www-form-urlencoded',
        'Content-Length': Buffer.byteLength( libraryString )
      }
    };

    const request = https.request( requestOptions, res => resolve( res ) );
    request.on( 'error', function( e ) {
      grunt.log.writeln( 'There was a problem uploading the data to the website: ' + e.message );
      reject( e );
    } );

    // write data to request body
    request.write( libraryString );

    request.end();
  } );

  grunt.log.writeln( 'Sending license data to /services/add-simulation-libraries' );
  await requestPromise;
  grunt.log.writeln( 'Sending data complete' );

  // Sort to easily compare lists of repositoryNames with usedBy columns, to see which resources are used by everything.
  simTitles.sort();

  // If anything is used by every sim indicate that here
  for ( const entry in compositeCode ) {
    if ( compositeCode.hasOwnProperty( entry ) ) {
      compositeCode[ entry ].usedBy.sort();
      if ( _.isEqual( simTitles, compositeCode[ entry ].usedBy ) ) {
        compositeCode[ entry ].usedBy = 'all-sims'; // this is an annotation, not the vestigial all-sims repo
      }
    }
  }

  const licenseJSON = grunt.file.readJSON( '../sherpa/lib/license.json' );

  const codeOutput = [];
  const codeLicensesUsed = [];
  const mediaLicensesUsed = [];

  // Get a list of the library names
  const libraryNames = [];
  for ( const lib in licenseJSON ) {
    if ( licenseJSON.hasOwnProperty( lib ) ) {
      libraryNames.push( lib );
    }
  }

  // Use a case insensitive sort, see http://stackoverflow.com/questions/8996963/how-to-perform-case-insensitive-sorting-in-javascript
  libraryNames.sort( function( a, b ) {
    return a.toLowerCase().localeCompare( b.toLowerCase() );
  } );

  // Add info for each library to the MD report
  for ( let i = 0; i < libraryNames.length; i++ ) {
    const library = libraryNames[ i ];

    const lineElementsForLibrary = [
      '**' + library + '**',
      licenseJSON[ library ].text.join( '
' ),
      licenseJSON[ library ].projectURL,
      'License: [' + licenseJSON[ library ].license + '](licenses/' + library + '.txt)',
      'Notes: ' + licenseJSON[ library ].notes
    ];

    if ( licenseJSON[ library ].dependencies ) {
      lineElementsForLibrary.push( 'Dependencies: **' + licenseJSON[ library ].dependencies + '**' );
    }

    if ( compositeCode.hasOwnProperty( library ) && Array.isArray( compositeCode[ library ].usedBy ) ) {
      lineElementsForLibrary.push( 'Used by: ' + compositeCode[ library ].usedBy.join( ', ' ) );
    }

    // \n worked well when viewing GitHub markdown as an issue comment, but for unknown reasons
 is necessary when
    // viewing from https://github.com/phetsims/sherpa/blob/master/third-party-licenses.md
    codeOutput.push( lineElementsForLibrary.join( '
' ) );

    if ( codeLicensesUsed.indexOf( licenseJSON[ library ].license ) < 0 ) {
      codeLicensesUsed.push( licenseJSON[ library ].license );
    }
  }

  const mediaOutput = [];
  const mediaKeys = [];
  for ( const imageAudioEntry in compositeMedia ) {
    if ( compositeMedia.hasOwnProperty( imageAudioEntry ) ) {
      mediaKeys.push( imageAudioEntry );
    }
  }
  // Use a case insensitive sort, see http://stackoverflow.com/questions/8996963/how-to-perform-case-insensitive-sorting-in-javascript
  mediaKeys.sort( function( a, b ) {
    return a.toLowerCase().localeCompare( b.toLowerCase() );
  } );

  // Create the text for the image and sound, and keep track of which licenses were used by them.
  for ( let i = 0; i < mediaKeys.length; i++ ) {
    const mediaKey = mediaKeys[ i ];

    let text = compositeMedia[ mediaKey ].text.join( '
' ).trim();
    let projectURL = compositeMedia[ mediaKey ].projectURL.trim();

    if ( text.length === 0 ) {
      text = '(no text)';
    }

    if ( projectURL.length === 0 ) {
      projectURL = '(no project url)';
    }

    let notes = compositeMedia[ mediaKey ].notes.trim();
    if ( notes.length === 0 ) {
      notes = '(no notes)';
    }

    const license = compositeMedia[ mediaKey ].license.trim();
    assert && assert( license.length > 0, 'All media entries must have a license' );

    const mediaEntryLines = [
      '**' + mediaKey + '**',
      text,
      projectURL,
      'License: ' + license,
      'Notes: ' + notes
    ];

    // PhET has temporarily chosen to publish John Travoltage with incompatible licenses, so the reasons for
    // making the exceptions are noted here.  The new artwork is being developed now and the simulation
    // will be republished without exception cases soon.
    // This code will remain in case we have other exception cases in the future.
    if ( compositeMedia[ mediaKey ].exception ) {
      mediaEntryLines.push( 'Exception: ' + compositeMedia[ mediaKey ].exception );
    }

    // TODO: is this a real license?  Why is there logic here but nowhere else?  Perhaps files annotated with
    // TODO: contact phethelp@colorado.edu are leaking into the HTML files unnecessarily, see https://github.com/phetsims/sherpa/issues/64
    if ( license !== 'contact phethelp@colorado.edu' ) {
      mediaOutput.push( mediaEntryLines.join( '
' ) );

      if ( mediaLicensesUsed.indexOf( license ) < 0 ) {
        mediaLicensesUsed.push( license );
      }
    }
  }

  // Summarize licenses used
  const fileList = simTitles.join( '\n* ' );

  const outputString =
    'This report enumerates the third-party resources (code, images, sounds, etc) used in a set of simulations.\n' +
    '* [Third-party Code](#third-party-code)\n' +
    '* [Third-party Code License Summary](#third-party-code-license-summary)\n' +
    '* [Third-party Media](#third-party-media)\n' +
    '* [Third-party Media License Summary](#third-party-media-license-summary)\n' +
    '\n' +
    'This report is for the following simulations: \n\n* ' + fileList + '\n\nTo see the third party resources used in a particular published ' +
    'simulation, inspect the HTML file between the `' + ChipperConstants.START_THIRD_PARTY_LICENSE_ENTRIES + '` and `' + ChipperConstants.END_THIRD_PARTY_LICENSE_ENTRIES + '` ' +
    '(only exists in sim publications after Aug 7, 2015).\n' +
    '# Third-party Code:
\n' +
    codeOutput.join( '\n\n' ) + '\n\n' +

    '---\n' +

    '# Third-party Code License Summary:
\n' +
    codeLicensesUsed.join( '
' ) + '\n\n' +

    '---\n' +

    '# Third-party Media:
\n' +
    mediaOutput.join( '\n\n' ) + '\n\n' +

    '---\n' +

    '# Third-party Media License Summary:
\n' +
    mediaLicensesUsed.join( '
' ) + '\n\n';

  // Compare the file output to the existing file, and write & git commit only if different
  if ( !grunt.file.exists( outputFilename ) || grunt.file.read( outputFilename ) !== outputString ) {
    grunt.log.writeln( 'File output changed, writing file ' + outputFilename );
    grunt.file.write( outputFilename, outputString );
  }
  else {
    grunt.log.writeln( outputFilename + ' contents are the same.  No need to save.' );
  }

  /**
   * Given an HTML text, find the title attribute by parsing for <title>
   * @param {string} html
   * @returns {string}
   */
  function parseTitle( html ) {
    const startKey = '<title>';
    const endKey = '</title>';

    const startIndex = html.indexOf( startKey );
    const endIndex = html.indexOf( endKey );

    return html.substring( startIndex + startKey.length, endIndex ).trim();
  }

  /**
   * Add the source (images/sounds/media or code) entries to the destination object, keyed by name.
   * @param {string} repositoryName - the name of the repository, such as 'energy-skate-park-basics'
   * @param {Object} source - the object from which to read the entry
   * @param {Object} destination - the object to which to append the entry
   */
  function augment( repositoryName, source, destination ) {
    for ( const entry in source ) {
      if ( source.hasOwnProperty( entry ) ) {
        if ( !destination.hasOwnProperty( entry ) ) {
          destination[ entry ] = source[ entry ];//overwrites
          destination[ entry ].usedBy = [];
        }
        destination[ entry ].usedBy.push( repositoryName );
      }
    }
  }
};<commit_msg>Remove unnecessary TODO, see https://github.com/phetsims/sherpa/issues/78<commit_after>// Copyright 2015-2020, University of Colorado Boulder

/**
 * Creates a composite report of all of the 3rd party images, code, sounds and other media used by all of the published
 * PhET Simulations. The reports is published at: https://github.com/phetsims/sherpa/blob/master/third-party-licenses.md
 *
 * Usage:
 * grunt report-third-party
 * // then push sherpa/third-party-licenses.md
 *
 * Third party entries are parsed from the HTML files for all simulations published on the PhET website.
 * See getLicenseEntry.js for documentation of the fields in the entries.
 *
 * Copy the local-auth-code key value from phet-server:/etc/tomcat/context.xml into the value for
 * websiteAuthorizationCode in ~/.phet/build-local.json
 *
 * @author Sam Reid
 * @author Jonathan Olson <jonathan.olson@colorado.edu>
 */

'use strict';

const _ = require( 'lodash' ); // eslint-disable-line require-statement-match
const assert = require( 'assert' );
const ChipperConstants = require( '../common/ChipperConstants' );
const fs = require( 'fs' );
const grunt = require( 'grunt' );
const https = require( 'https' );
const rp = require( 'request-promise-native' ); // eslint-disable-line require-statement-match

/**
 * @returns {Promise} - Resolves when complete.
 */
module.exports = async function() {

  // read configuration file - required to write to website database
  const serverName = 'phet.colorado.edu';
  const BUILD_LOCAL_FILENAME = process.env.HOME + '/.phet/build-local.json';
  const buildLocalJSON = JSON.parse( fs.readFileSync( BUILD_LOCAL_FILENAME, { encoding: 'utf-8' } ) );
  assert( buildLocalJSON && buildLocalJSON.websiteAuthorizationCode, 'websiteAuthorizationCode missing from ' + BUILD_LOCAL_FILENAME );

  // The file where the report will be written
  const outputFilename = '../sherpa/third-party-licenses.md';

  // Aggregate results for each of the license types
  const compositeCode = {};
  const compositeMedia = {};

  // List of all of the repository names, so that we can detect which libraries are used by all-sims
  const simTitles = [];

  // List of libraries for each sim
  // Type: string in JSON format
  const simLibraries = [];

  // Download all sims. If it's not published, it will be skipped in the report
  const activeSims = fs.readFileSync( '../perennial/data/active-sims', 'utf-8' ).trim().split( '\n' ).map( sim => sim.trim() );

  for ( const sim of activeSims ) {
    const url = `https://${serverName}/sims/html/${sim}/latest/${sim}_en.html`;
    console.log( `downloading ${sim}` );
    try {
      const html = ( await rp( url ) ).trim();

      const startIndex = html.indexOf( ChipperConstants.START_THIRD_PARTY_LICENSE_ENTRIES );
      const endIndex = html.indexOf( ChipperConstants.END_THIRD_PARTY_LICENSE_ENTRIES );
      const substring = html.substring( startIndex, endIndex );

      const firstCurlyBrace = substring.indexOf( '{' );
      const lastCurlyBrace = substring.lastIndexOf( '}' );
      const jsonString = substring.substring( firstCurlyBrace, lastCurlyBrace + 1 );

      const json = JSON.parse( jsonString );

      let title = parseTitle( html );
      if ( !title || title.indexOf( 'undefined' ) === 0 || title.indexOf( 'TITLE' ) >= 0 ) {
        grunt.log.writeln( `title not found for ${sim}` );
        title = sim;
      }
      augment( title, json.lib, compositeCode );
      augment( title, json.sounds, compositeMedia );
      augment( title, json.images, compositeMedia );

      simTitles.push( title );

      // Concatenate all the libraries for this sim with html newline.
      let libString = '';
      for ( const entry in json.lib ) {
        libString += entry + '
';
      }

      //  Update the object to be pushed to the website database
      simLibraries.push( {
        name: sim,
        libraries: libString
      } );
    }
    catch( e ) {
      console.log( `${sim} not found on production` );
    }
  }

  const requestPromise = new Promise( ( resolve, reject ) => {
    // Change libraryobject to string in format that the database will recognize.
    // i.e. '{"sim-name":"Library Name
Library Name", ...}'
    const libraryString = '{' + simLibraries.map( o => `"${o.name}":"${o.libraries}"` ).join( ',' ) + '}';

    const requestOptions = {
      host: serverName,
      path: '/services/add-simulation-libraries',
      port: 443,
      method: 'POST',
      auth: 'token:' + buildLocalJSON.websiteAuthorizationCode,
      headers: {
        'Content-Type': 'application/x-www-form-urlencoded',
        'Content-Length': Buffer.byteLength( libraryString )
      }
    };

    const request = https.request( requestOptions, res => resolve( res ) );
    request.on( 'error', function( e ) {
      grunt.log.writeln( 'There was a problem uploading the data to the website: ' + e.message );
      reject( e );
    } );

    // write data to request body
    request.write( libraryString );

    request.end();
  } );

  grunt.log.writeln( 'Sending license data to /services/add-simulation-libraries' );
  await requestPromise;
  grunt.log.writeln( 'Sending data complete' );

  // Sort to easily compare lists of repositoryNames with usedBy columns, to see which resources are used by everything.
  simTitles.sort();

  // If anything is used by every sim indicate that here
  for ( const entry in compositeCode ) {
    if ( compositeCode.hasOwnProperty( entry ) ) {
      compositeCode[ entry ].usedBy.sort();
      if ( _.isEqual( simTitles, compositeCode[ entry ].usedBy ) ) {
        compositeCode[ entry ].usedBy = 'all-sims'; // this is an annotation, not the vestigial all-sims repo
      }
    }
  }

  const licenseJSON = grunt.file.readJSON( '../sherpa/lib/license.json' );

  const codeOutput = [];
  const codeLicensesUsed = [];
  const mediaLicensesUsed = [];

  // Get a list of the library names
  const libraryNames = [];
  for ( const lib in licenseJSON ) {
    if ( licenseJSON.hasOwnProperty( lib ) ) {
      libraryNames.push( lib );
    }
  }

  // Use a case insensitive sort, see http://stackoverflow.com/questions/8996963/how-to-perform-case-insensitive-sorting-in-javascript
  libraryNames.sort( function( a, b ) {
    return a.toLowerCase().localeCompare( b.toLowerCase() );
  } );

  // Add info for each library to the MD report
  for ( let i = 0; i < libraryNames.length; i++ ) {
    const library = libraryNames[ i ];

    const lineElementsForLibrary = [
      '**' + library + '**',
      licenseJSON[ library ].text.join( '
' ),
      licenseJSON[ library ].projectURL,
      'License: [' + licenseJSON[ library ].license + '](licenses/' + library + '.txt)',
      'Notes: ' + licenseJSON[ library ].notes
    ];

    if ( licenseJSON[ library ].dependencies ) {
      lineElementsForLibrary.push( 'Dependencies: **' + licenseJSON[ library ].dependencies + '**' );
    }

    if ( compositeCode.hasOwnProperty( library ) && Array.isArray( compositeCode[ library ].usedBy ) ) {
      lineElementsForLibrary.push( 'Used by: ' + compositeCode[ library ].usedBy.join( ', ' ) );
    }

    // \n worked well when viewing GitHub markdown as an issue comment, but for unknown reasons
 is necessary when
    // viewing from https://github.com/phetsims/sherpa/blob/master/third-party-licenses.md
    codeOutput.push( lineElementsForLibrary.join( '
' ) );

    if ( codeLicensesUsed.indexOf( licenseJSON[ library ].license ) < 0 ) {
      codeLicensesUsed.push( licenseJSON[ library ].license );
    }
  }

  const mediaOutput = [];
  const mediaKeys = [];
  for ( const imageAudioEntry in compositeMedia ) {
    if ( compositeMedia.hasOwnProperty( imageAudioEntry ) ) {
      mediaKeys.push( imageAudioEntry );
    }
  }
  // Use a case insensitive sort, see http://stackoverflow.com/questions/8996963/how-to-perform-case-insensitive-sorting-in-javascript
  mediaKeys.sort( function( a, b ) {
    return a.toLowerCase().localeCompare( b.toLowerCase() );
  } );

  // Create the text for the image and sound, and keep track of which licenses were used by them.
  for ( let i = 0; i < mediaKeys.length; i++ ) {
    const mediaKey = mediaKeys[ i ];

    let text = compositeMedia[ mediaKey ].text.join( '
' ).trim();
    let projectURL = compositeMedia[ mediaKey ].projectURL.trim();

    if ( text.length === 0 ) {
      text = '(no text)';
    }

    if ( projectURL.length === 0 ) {
      projectURL = '(no project url)';
    }

    let notes = compositeMedia[ mediaKey ].notes.trim();
    if ( notes.length === 0 ) {
      notes = '(no notes)';
    }

    const license = compositeMedia[ mediaKey ].license.trim();
    assert && assert( license.length > 0, 'All media entries must have a license' );

    const mediaEntryLines = [
      '**' + mediaKey + '**',
      text,
      projectURL,
      'License: ' + license,
      'Notes: ' + notes
    ];

    // PhET has temporarily chosen to publish John Travoltage with incompatible licenses, so the reasons for
    // making the exceptions are noted here.  The new artwork is being developed now and the simulation
    // will be republished without exception cases soon.
    // This code will remain in case we have other exception cases in the future.
    if ( compositeMedia[ mediaKey ].exception ) {
      mediaEntryLines.push( 'Exception: ' + compositeMedia[ mediaKey ].exception );
    }

    if ( license !== 'contact phethelp@colorado.edu' ) {
      mediaOutput.push( mediaEntryLines.join( '
' ) );

      if ( mediaLicensesUsed.indexOf( license ) < 0 ) {
        mediaLicensesUsed.push( license );
      }
    }
  }

  // Summarize licenses used
  const fileList = simTitles.join( '\n* ' );

  const outputString =
    'This report enumerates the third-party resources (code, images, sounds, etc) used in a set of simulations.\n' +
    '* [Third-party Code](#third-party-code)\n' +
    '* [Third-party Code License Summary](#third-party-code-license-summary)\n' +
    '* [Third-party Media](#third-party-media)\n' +
    '* [Third-party Media License Summary](#third-party-media-license-summary)\n' +
    '\n' +
    'This report is for the following simulations: \n\n* ' + fileList + '\n\nTo see the third party resources used in a particular published ' +
    'simulation, inspect the HTML file between the `' + ChipperConstants.START_THIRD_PARTY_LICENSE_ENTRIES + '` and `' + ChipperConstants.END_THIRD_PARTY_LICENSE_ENTRIES + '` ' +
    '(only exists in sim publications after Aug 7, 2015).\n' +
    '# Third-party Code:
\n' +
    codeOutput.join( '\n\n' ) + '\n\n' +

    '---\n' +

    '# Third-party Code License Summary:
\n' +
    codeLicensesUsed.join( '
' ) + '\n\n' +

    '---\n' +

    '# Third-party Media:
\n' +
    mediaOutput.join( '\n\n' ) + '\n\n' +

    '---\n' +

    '# Third-party Media License Summary:
\n' +
    mediaLicensesUsed.join( '
' ) + '\n\n';

  // Compare the file output to the existing file, and write & git commit only if different
  if ( !grunt.file.exists( outputFilename ) || grunt.file.read( outputFilename ) !== outputString ) {
    grunt.log.writeln( 'File output changed, writing file ' + outputFilename );
    grunt.file.write( outputFilename, outputString );
  }
  else {
    grunt.log.writeln( outputFilename + ' contents are the same.  No need to save.' );
  }

  /**
   * Given an HTML text, find the title attribute by parsing for <title>
   * @param {string} html
   * @returns {string}
   */
  function parseTitle( html ) {
    const startKey = '<title>';
    const endKey = '</title>';

    const startIndex = html.indexOf( startKey );
    const endIndex = html.indexOf( endKey );

    return html.substring( startIndex + startKey.length, endIndex ).trim();
  }

  /**
   * Add the source (images/sounds/media or code) entries to the destination object, keyed by name.
   * @param {string} repositoryName - the name of the repository, such as 'energy-skate-park-basics'
   * @param {Object} source - the object from which to read the entry
   * @param {Object} destination - the object to which to append the entry
   */
  function augment( repositoryName, source, destination ) {
    for ( const entry in source ) {
      if ( source.hasOwnProperty( entry ) ) {
        if ( !destination.hasOwnProperty( entry ) ) {
          destination[ entry ] = source[ entry ];//overwrites
          destination[ entry ].usedBy = [];
        }
        destination[ entry ].usedBy.push( repositoryName );
      }
    }
  }
};<|endoftext|>


<commit_before>/*
* jQuery Mobile Framework : prototype for "fixHeaderFooter" plugin - on-demand positioning for headers,footers
* Copyright (c) jQuery Project
* Dual licensed under the MIT (MIT-LICENSE.txt) and GPL (GPL-LICENSE.txt) licenses.
* Note: Code is in draft form and is subject to change
*/
(function($){
$.fn.fixHeaderFooter = function(options){
	return $(this).each(function(){
		//make sure these things only get wrapped once
		if($(this).data('fixedapplied')){ return; }
		var el = $uipage = $(this).data('fixedapplied',true),
			els = el.find('.ui-header,.ui-footer').wrap('

');
			//wrapper explanation: placehold wrapper sits inline and holds the height of the toolbars open while they are position abs,
			//unless it's overlay-only, in which case the height is 0 so it's hidden when pos static

		//config options - currently driven off data attr's
		var o = $.extend({
			transition: el.find('[data-headfoottransition]').attr('data-headfoottransition') || ['slidedown','slideup'],
			//also accepts a string, like 'fade'. All animations work, but fade and slidedown/up look best
			overlayOnly: el.find('.ui-fullscreen').length
		},options);

		//add transition types
		els.filter('.ui-header').addClass( $.isArray(o.transition) ? o.transition[0] : o.transition);
		els.filter('.ui-footer').addClass( $.isArray(o.transition) ? o.transition[1] : o.transition);

		//set placeholder heights, then bind custom events
		els
			.each(function(){
				var placehold = $(this).parents('.ui-headfoot-placehold:eq(0)');
				if(o.overlayOnly){
					placehold.height(0).addClass('ui-headfoot-overlayonly');
				}
				else{
					placehold.height($(this).parent().height());
				}
			})
			.bind('setTop',function(){
				var fromTop = $(window).scrollTop(),
					screenHeight = window.innerHeight,
					thisHeight = $(this).parent().height();
					return $(this).parent().css('top', ($(this).is('.ui-header')) ? fromTop : fromTop + screenHeight - thisHeight);
			});
	});
};

//single controller for all showing,hiding,toggling
$.fixedToolbars = (function(){

		var currentstate = 'inline',
			showAfterScroll = false,
			delayTimer,
			ignoreTargets = 'a,input,textarea,select,button,label,.ui-headfoot-placehold';

		function allToolbars(){
			return $('.ui-header,.ui-footer');
		}

		//for determining whether a placeholder is visible on the screen or not
		function placeHolderOutofView(thisel){
			//always return true if it's overlayOnly
			if(thisel.closest('.ui-headfoot-overlayonly').length){ return true; }

			var fromTop = $(window).scrollTop(),
				screenHeight = window.innerHeight,
				thisHeight = thisel.parent().parent().height(),
				thisTop = thisel.parent().parent().offset().top;

			return thisel.is('.ui-header') ? (thisTop + thisHeight <= fromTop) : (thisTop > fromTop + screenHeight);
		}

	$(document)
		.bind('tap',function(e){
			if( !$(e.target).closest(ignoreTargets).length ){
				$.fixedToolbars.toggle();
			}
		})
		.bind('scrollstart',function(){
			if(currentstate == 'overlay'){
				showAfterScroll = true;
			}
			$.fixedToolbars.hide(true);
		})
		.bind('scrollstop',function(){
			if(showAfterScroll){
				$.fixedToolbars.show();
				showAfterScroll = false;
			}
		});

		//hide on resize?
		$(window).resize(function(){ $.fixedToolbars.hide(true); });

	return {
		show: function(){
			if(currentstate == 'overlay'){ return; }
			currentstate = 'overlay';
			var els = allToolbars();
			return els.each(function(){
				var el = $(this),
					overlayOnly = el.closest('.ui-headfoot-overlayonly').length;
				el.parent().addClass('ui-fixpos');
				//only animate if placeholder is out of view
				if( placeHolderOutofView(el) ){
					el.addClass('in').animationComplete(function(){
						el.removeClass('in');
					});
				}
				if(overlayOnly){
					el.parent().parent().removeClass('ui-headfoot-hidden');
				}
				el.trigger('setTop');
			});
		},
		hide: function(immediately){
			if(currentstate == 'inline'){ return; }
			currentstate = 'inline';
			var els = allToolbars();
			return els.each(function(){
				var el = $(this),
					overlayOnly = el.closest('.ui-headfoot-overlayonly').length;

				function addOverlayOnlyClass(){
					if(overlayOnly){
						el.parent().parent().addClass('ui-headfoot-hidden');
					}
				}
				//if immediately flag is true, or the placeholder is in view, don't animate the hide
				if(immediately || !placeHolderOutofView(el)){
					el.parent().removeClass('ui-fixpos');
					addOverlayOnlyClass();
				}
				else{
					var classes = 'out';
					//if it's one of these, we'll need to add the reverse class too
					if(el.is('.slidedown,.slideup, .swap, .pop, .flip')){
						classes += ' reverse';
					}
					el.addClass(classes).animationComplete(function(){
						el.removeClass(classes);
						el.parent().removeClass('ui-fixpos');
						addOverlayOnlyClass();
					});
				}
			});
		},
		hideAfterDelay: function(){
			delayTimer = setTimeout(function(){
				$.fixedToolbars.hide();
			}, 3000);
		},
		toggle: function(){
			return (currentstate == 'overlay') ? $.fixedToolbars.hide() : $.fixedToolbars.show();
		}
	};
})();

})(jQuery);<commit_msg>Fixed event binding for fixed toolbars.<commit_after>/*
* jQuery Mobile Framework : prototype for "fixHeaderFooter" plugin - on-demand positioning for headers,footers
* Copyright (c) jQuery Project
* Dual licensed under the MIT (MIT-LICENSE.txt) and GPL (GPL-LICENSE.txt) licenses.
* Note: Code is in draft form and is subject to change
*/
(function($){
$.fn.fixHeaderFooter = function(options){
	return $(this).each(function(){
		//make sure these things only get wrapped once
		if($(this).data('fixedapplied')){ return; }
		var el = $uipage = $(this).data('fixedapplied',true),
			els = el.find('.ui-header,.ui-footer').wrap('

');
			//wrapper explanation: placehold wrapper sits inline and holds the height of the toolbars open while they are position abs,
			//unless it's overlay-only, in which case the height is 0 so it's hidden when pos static

		//config options - currently driven off data attr's
		var o = $.extend({
			transition: el.find('[data-headfoottransition]').attr('data-headfoottransition') || ['slidedown','slideup'],
			//also accepts a string, like 'fade'. All animations work, but fade and slidedown/up look best
			overlayOnly: el.find('.ui-fullscreen').length
		},options);

		//add transition types
		els.filter('.ui-header').addClass( $.isArray(o.transition) ? o.transition[0] : o.transition);
		els.filter('.ui-footer').addClass( $.isArray(o.transition) ? o.transition[1] : o.transition);

		//set placeholder heights, then bind custom events
		els
			.each(function(){
				var placehold = $(this).parents('.ui-headfoot-placehold:eq(0)');
				if(o.overlayOnly){
					placehold.height(0).addClass('ui-headfoot-overlayonly');
				}
				else{
					placehold.height($(this).parent().height());
				}
			})
			.bind('setTop',function(){
				var fromTop = $(window).scrollTop(),
					screenHeight = window.innerHeight,
					thisHeight = $(this).parent().height();
					return $(this).parent().css('top', ($(this).is('.ui-header')) ? fromTop : fromTop + screenHeight - thisHeight);
			});
	});
};

//single controller for all showing,hiding,toggling
$.fixedToolbars = (function(){

		var currentstate = 'inline',
			showAfterScroll = false,
			delayTimer,
			ignoreTargets = 'a,input,textarea,select,button,label,.ui-headfoot-placehold';

		function allToolbars(){
			return $('.ui-header,.ui-footer');
		}

		//for determining whether a placeholder is visible on the screen or not
		function placeHolderOutofView(thisel){
			//always return true if it's overlayOnly
			if(thisel.closest('.ui-headfoot-overlayonly').length){ return true; }

			var fromTop = $(window).scrollTop(),
				screenHeight = window.innerHeight,
				thisHeight = thisel.parent().parent().height(),
				thisTop = thisel.parent().parent().offset().top;

			return thisel.is('.ui-header') ? (thisTop + thisHeight <= fromTop) : (thisTop > fromTop + screenHeight);
		}

	$(function() {
	$(document)
		.bind('tap',function(e){
			if( !$(e.target).closest(ignoreTargets).length ){
				$.fixedToolbars.toggle();
			}
		})
		.bind('scrollstart',function(){
			if(currentstate == 'overlay'){
				showAfterScroll = true;
			}
			$.fixedToolbars.hide(true);
		})
		.bind('scrollstop',function(){
			if(showAfterScroll){
				$.fixedToolbars.show();
				showAfterScroll = false;
			}
		});
	});

		//hide on resize?
		$(window).resize(function(){ $.fixedToolbars.hide(true); });

	return {
		show: function(){
			if(currentstate == 'overlay'){ return; }
			currentstate = 'overlay';
			var els = allToolbars();
			return els.each(function(){
				var el = $(this),
					overlayOnly = el.closest('.ui-headfoot-overlayonly').length;
				el.parent().addClass('ui-fixpos');
				//only animate if placeholder is out of view
				if( placeHolderOutofView(el) ){
					el.addClass('in').animationComplete(function(){
						el.removeClass('in');
					});
				}
				if(overlayOnly){
					el.parent().parent().removeClass('ui-headfoot-hidden');
				}
				el.trigger('setTop');
			});
		},
		hide: function(immediately){
			if(currentstate == 'inline'){ return; }
			currentstate = 'inline';
			var els = allToolbars();
			return els.each(function(){
				var el = $(this),
					overlayOnly = el.closest('.ui-headfoot-overlayonly').length;

				function addOverlayOnlyClass(){
					if(overlayOnly){
						el.parent().parent().addClass('ui-headfoot-hidden');
					}
				}
				//if immediately flag is true, or the placeholder is in view, don't animate the hide
				if(immediately || !placeHolderOutofView(el)){
					el.parent().removeClass('ui-fixpos');
					addOverlayOnlyClass();
				}
				else{
					var classes = 'out';
					//if it's one of these, we'll need to add the reverse class too
					if(el.is('.slidedown,.slideup, .swap, .pop, .flip')){
						classes += ' reverse';
					}
					el.addClass(classes).animationComplete(function(){
						el.removeClass(classes);
						el.parent().removeClass('ui-fixpos');
						addOverlayOnlyClass();
					});
				}
			});
		},
		hideAfterDelay: function(){
			delayTimer = setTimeout(function(){
				$.fixedToolbars.hide();
			}, 3000);
		},
		toggle: function(){
			return (currentstate == 'overlay') ? $.fixedToolbars.hide() : $.fixedToolbars.show();
		}
	};
})();

})(jQuery);<|endoftext|>


<commit_before>function KeyboardInputManager() {
  this.events = {};

  if (window.navigator.msPointerEnabled) {
    //Internet Explorer 10 style
    this.eventTouchstart    = "MSPointerDown";
    this.eventTouchmove     = "MSPointerMove";
    this.eventTouchend      = "MSPointerUp";
  } else {
    this.eventTouchstart    = "touchstart";
    this.eventTouchmove     = "touchmove";
    this.eventTouchend      = "touchend";
  }

  this.listen();
}

KeyboardInputManager.prototype.on = function (event, callback) {
  if (!this.events[event]) {
    this.events[event] = [];
  }
  this.events[event].push(callback);
};

KeyboardInputManager.prototype.emit = function (event, data) {
  var callbacks = this.events[event];
  if (callbacks) {
    callbacks.forEach(function (callback) {
      callback(data);
    });
  }
};

KeyboardInputManager.prototype.listen = function () {
  var self = this;

  var map = {
    38: 0, // Up
    39: 1, // Right
    40: 2, // Down
    37: 3, // Left
    75: 0, // Vim up
    76: 1, // Vim right
    74: 2, // Vim down
    72: 3, // Vim left
    87: 0, // W
    68: 1, // D
    83: 2, // S
    65: 3  // A
  };

  // Respond to direction keys
  document.addEventListener("keydown", function (event) {
    var modifiers = event.altKey || event.ctrlKey || event.metaKey ||
                    event.shiftKey;
    var mapped    = map[event.which];

    if (!modifiers) {
      if (mapped !== undefined) {
        event.preventDefault();
        self.emit("move", mapped);
      }
    }

    // R key restarts the game
    if (!modifiers && event.which === 82) {
      self.restart.call(self, event);
    }
  });

  // Respond to button presses
  this.bindButtonPress(".retry-button", this.restart);
  this.bindButtonPress(".restart-button", this.restart);
  this.bindButtonPress(".keep-playing-button", this.keepPlaying);

  // Respond to swipe events
  var touchStartClientX, touchStartClientY;
  var gameContainer = document.getElementsByClassName("game-container")[0];

  gameContainer.addEventListener(this.eventTouchstart, function (event) {
    if ((!window.navigator.msPointerEnabled && event.touches.length > 1) ||
        event.targetTouches > 1) {
      return; // Ignore if touching with more than 1 finger
    }

    if (window.navigator.msPointerEnabled) {
      touchStartClientX = event.pageX;
      touchStartClientY = event.pageY;
    } else {
      touchStartClientX = event.touches[0].clientX;
      touchStartClientY = event.touches[0].clientY;
    }

    event.preventDefault();
  });

  gameContainer.addEventListener(this.eventTouchmove, function (event) {
    event.preventDefault();
  });

  gameContainer.addEventListener(this.eventTouchend, function (event) {
    if ((!window.navigator.msPointerEnabled && event.touches.length > 0) ||
        event.targetTouches > 0) {
      return; // Ignore if still touching with one or more fingers
    }

    var touchEndClientX, touchEndClientY;

    if (window.navigator.msPointerEnabled) {
      touchEndClientX = event.pageX;
      touchEndClientY = event.pageY;
    } else {
      touchEndClientX = event.changedTouches[0].clientX;
      touchEndClientY = event.changedTouches[0].clientY;
    }

    var dx = touchEndClientX - touchStartClientX;
    var absDx = Math.abs(dx);

    var dy = touchEndClientY - touchStartClientY;
    var absDy = Math.abs(dy);

    if (Math.max(absDx, absDy) > 10) {
      // (right : left) : (down : up)
      self.emit("move", absDx > absDy ? (dx > 0 ? 1 : 3) : (dy > 0 ? 2 : 0));
    }
  });
};

KeyboardInputManager.prototype.restart = function (event) {
  event.preventDefault();
  this.emit("restart");
};

KeyboardInputManager.prototype.keepPlaying = function (event) {
  event.preventDefault();
  this.emit("keepPlaying");
};

KeyboardInputManager.prototype.bindButtonPress = function (selector, fn) {
  var self = this;
  document.querySelectorAll(selector).forEach(function(button) {
	button.addEventListener("click", fn.bind(self));
    button.addEventListener(this.eventTouchend, fn.bind(self));
  });
};
<commit_msg>Fix touch not work issue<commit_after>function KeyboardInputManager() {
  this.events = {};

  if (window.navigator.msPointerEnabled) {
    //Internet Explorer 10 style
    this.eventTouchstart    = "MSPointerDown";
    this.eventTouchmove     = "MSPointerMove";
    this.eventTouchend      = "MSPointerUp";
  } else {
    this.eventTouchstart    = "touchstart";
    this.eventTouchmove     = "touchmove";
    this.eventTouchend      = "touchend";
  }

  this.listen();
}

KeyboardInputManager.prototype.on = function (event, callback) {
  if (!this.events[event]) {
    this.events[event] = [];
  }
  this.events[event].push(callback);
};

KeyboardInputManager.prototype.emit = function (event, data) {
  var callbacks = this.events[event];
  if (callbacks) {
    callbacks.forEach(function (callback) {
      callback(data);
    });
  }
};

KeyboardInputManager.prototype.listen = function () {
  var self = this;

  var map = {
    38: 0, // Up
    39: 1, // Right
    40: 2, // Down
    37: 3, // Left
    75: 0, // Vim up
    76: 1, // Vim right
    74: 2, // Vim down
    72: 3, // Vim left
    87: 0, // W
    68: 1, // D
    83: 2, // S
    65: 3  // A
  };

  // Respond to direction keys
  document.addEventListener("keydown", function (event) {
    var modifiers = event.altKey || event.ctrlKey || event.metaKey ||
                    event.shiftKey;
    var mapped    = map[event.which];

    if (!modifiers) {
      if (mapped !== undefined) {
        event.preventDefault();
        self.emit("move", mapped);
      }
    }

    // R key restarts the game
    if (!modifiers && event.which === 82) {
      self.restart.call(self, event);
    }
  });

  // Respond to button presses
  this.bindButtonPress(".retry-button", this.restart);
  this.bindButtonPress(".restart-button", this.restart);
  this.bindButtonPress(".keep-playing-button", this.keepPlaying);

  // Respond to swipe events
  var touchStartClientX, touchStartClientY;
  var gameContainer = document.getElementsByClassName("game-container")[0];

  gameContainer.addEventListener(this.eventTouchstart, function (event) {
    if ((!window.navigator.msPointerEnabled && event.touches.length > 1) ||
        event.targetTouches > 1) {
      return; // Ignore if touching with more than 1 finger
    }

    if (window.navigator.msPointerEnabled) {
      touchStartClientX = event.pageX;
      touchStartClientY = event.pageY;
    } else {
      touchStartClientX = event.touches[0].clientX;
      touchStartClientY = event.touches[0].clientY;
    }

    event.preventDefault();
  });

  gameContainer.addEventListener(this.eventTouchmove, function (event) {
    event.preventDefault();
  });

  gameContainer.addEventListener(this.eventTouchend, function (event) {
    if ((!window.navigator.msPointerEnabled && event.touches.length > 0) ||
        event.targetTouches > 0) {
      return; // Ignore if still touching with one or more fingers
    }

    var touchEndClientX, touchEndClientY;

    if (window.navigator.msPointerEnabled) {
      touchEndClientX = event.pageX;
      touchEndClientY = event.pageY;
    } else {
      touchEndClientX = event.changedTouches[0].clientX;
      touchEndClientY = event.changedTouches[0].clientY;
    }

    var dx = touchEndClientX - touchStartClientX;
    var absDx = Math.abs(dx);

    var dy = touchEndClientY - touchStartClientY;
    var absDy = Math.abs(dy);

    if (Math.max(absDx, absDy) > 10) {
      // (right : left) : (down : up)
      self.emit("move", absDx > absDy ? (dx > 0 ? 1 : 3) : (dy > 0 ? 2 : 0));
    }
  });
};

KeyboardInputManager.prototype.restart = function (event) {
  event.preventDefault();
  this.emit("restart");
};

KeyboardInputManager.prototype.keepPlaying = function (event) {
  event.preventDefault();
  this.emit("keepPlaying");
};

KeyboardInputManager.prototype.bindButtonPress = function (selector, fn) {
  var self = this;
  document.querySelectorAll(selector).forEach(function(button) {
	button.addEventListener("click", fn.bind(self));
    button.addEventListener(self.eventTouchend, fn.bind(self));
  });
};
<|endoftext|>