<commit_before>
var _ = require('underscore');
var Promise = require('bluebird').Promise;
var vcr = require('nock-vcr-recorder-mocha');
var Sdk = require('./../../../lib/clc-sdk.js');
var compute = new Sdk(/*'cloud_user', 'cloud_user_password'*/).computeServices();
var assert = require('assert');

vcr.describe('Modify Group Operation [UNIT1]', function () {
    var groups = compute.groups();

    vcr.it('Should modify group name', function (done) {
        this.timeout(50 * 1000);

        Promise.resolve()
            .then(_.partial(createGroup, {name: 'Group1'}))

            .then(_.partial(modifyGroup, {name: 'Group2'}))

            .then(assertThatGroupNameIs('Group2'))

            .then(deleteGroup(done));
    });

    vcr.it('Should modify group name and description together by one call', function (done) {
        this.timeout(50 * 1000);

        Promise.resolve()
            .then(_.partial(createGroup, {name: 'Group1', description: 'Desc1'}))

            .then(_.partial(modifyGroup, {name: 'Group2', description: 'Desc2'}))

            .then(assertThatGroupNameIs('Group2'))
            .then(assertThatDescriptionIs('Desc2'))

            .then(deleteGroup(done));
    });

    function assertGroup (callback) {
        return function (group) {
            return groups
                .findSingle(group)
                .then(function (metadata) {
                    callback(metadata);
                    return group;
                });
        };
    }

    function assertThatDescriptionIs(expectedDescription) {
        return assertGroup(function (metadata) {
            assert.equal(metadata.description, expectedDescription);
        });
    }

    function createGroup (config) {
        return groups
            .create(_.extend({
                parentGroup: {
                    dataCenter: compute.DataCenter.DE_FRANKFURT,
                    name: compute.Group.DEFAULT
                },
                name: 'Group1',
                description: 'Test Group'
            }, config))
            .then(assertThatGroupRefIsCorrect);
    }

    function assertThatGroupNameIs (name) {
        return assertGroup(function (metadata) {
            assert.equal(metadata.name, name);
        });
    }

    function modifyGroup(config, group) {
        return groups.modify(group, config);
    }

    function assertThatGroupRefIsCorrect (groupRef) {
        assert(!_.isUndefined(groupRef.id));

        return groupRef;
    }

    function deleteGroup (done) {
        return function (groupCriteria) {
            compute.groups().delete(groupCriteria);
            done();

            return groupCriteria;
        };
    }
});<commit_msg>#41 Implement possibilities to modify group<commit_after>
var _ = require('underscore');
var Promise = require('bluebird').Promise;
var vcr = require('nock-vcr-recorder-mocha');
var Sdk = require('./../../../lib/clc-sdk.js');
var compute = new Sdk(/*'cloud_user', 'cloud_user_password'*/).computeServices();
var assert = require('assert');

vcr.describe('Modify Group Operation [UNIT1]', function () {
    var groups = compute.groups();

    vcr.it('Should modify group name', function (done) {
        this.timeout(50 * 1000);

        Promise.resolve()
            .then(_.partial(createGroup, {name: 'Group1'}))

            .then(_.partial(modifyGroup, {name: 'Group2'}))

            .then(assertThatGroupRefIsCorrect)
            .then(assertThatGroupNameIs('Group2'))

            .then(deleteGroup(done));
    });

    vcr.it('Should modify group name and description together by one call', function (done) {
        this.timeout(50 * 1000);

        Promise.resolve()
            .then(_.partial(createGroup, {name: 'Group1', description: 'Desc1'}))

            .then(_.partial(modifyGroup, {name: 'Group2', description: 'Desc2'}))

            .then(assertThatGroupNameIs('Group2'))
            .then(assertThatDescriptionIs('Desc2'))

            .then(deleteGroup(done));
    });

    function assertGroup (callback) {
        return function (group) {
            return groups
                .findSingle(group)
                .then(function (metadata) {
                    callback(metadata);
                    return group;
                });
        };
    }

    function assertThatDescriptionIs(expectedDescription) {
        return assertGroup(function (metadata) {
            assert.equal(metadata.description, expectedDescription);
        });
    }

    function createGroup (config) {
        return groups
            .create(_.extend({
                parentGroup: {
                    dataCenter: compute.DataCenter.DE_FRANKFURT,
                    name: compute.Group.DEFAULT
                },
                name: 'Group1',
                description: 'Test Group'
            }, config))
            .then(assertThatGroupRefIsCorrect);
    }

    function assertThatGroupNameIs (name) {
        return assertGroup(function (metadata) {
            assert.equal(metadata.name, name);
        });
    }

    function modifyGroup(config, group) {
        return groups.modify(group, config);
    }

    function assertThatGroupRefIsCorrect (groupRef) {
        assert(!_.isUndefined(groupRef.id));

        return groupRef;
    }

    function deleteGroup (done) {
        return function (groupCriteria) {
            compute.groups().delete(groupCriteria);
            done();

            return groupCriteria;
        };
    }
});<|endoftext|>


<commit_before>(function ($) {
  function allFieldsValid() {
    var fields = [
      'BatchVol',
      'GBill',
      'HBill',
      'DHop',
      'BoilTime',
      'BoilRate',
      'TempGrain',
      'TempMash',
      'VolSparge',
      'PotSize',
      'KettleID',
      'LossTrub',
      'LossFermTrub',
      'HChilled',
      'VolPackaged',
      'Gabs',
      'Habs',
      'EBoil',
    ];
    for (i = 0; i < fields.length; i++) {
      if (!$('#' + fields[i]).val().match(/^d*(.\d+)?/)) {
        return false;
      }
    }
    return true;
  }
  function updateCalc() {
    if (!allFieldsValid()) {
      return;
    }
    var BatchVol = parseFloat($('#BatchVol').val()),
    GBill = parseFloat($('#GBill').val()),
    HBill = parseFloat($('#HBill').val()),
    DHop = parseFloat($('#DHop').val()),
    BoilTime = parseFloat($('#BoilTime').val()),
    BoilRate = parseFloat($('#BoilRate').val()),
    TempGrain = parseFloat($('#TempGrain').val()),
    TempMash = parseFloat($('#TempMash').val()),
    VolSparge = parseFloat($('#VolSparge').val()),
    PotSize = parseFloat($('#PotSize').val()),
    KettleID = parseFloat($('#KettleID').val()),
    LossTrub = parseFloat($('#LossTrub').val()),
    Gabs = parseFloat($('#Gabs').val()),
    Habs = parseFloat($('#Habs').val()),
    MashAdj = parseFloat($('#MashAdj').val()),
    StrikeAdj = parseFloat($('#StrikeAdj').val()),
    LossFermTrub = parseFloat($('#LossFermTrub').val()),
    LossBoil = BoilTime * BoilRate / 60,
    LossHop = HBill * Habs,
    LossGrain = GBill * Gabs,
    LossTot = LossGrain + LossHop + LossBoil + LossTrub,
    WaterTot = BatchVol + LossTot,
    VolStart = (WaterTot - VolSparge),
    TempStrike = TempMash + (0.05 * GBill / VolStart) * (TempMash - TempGrain),
    MashAdj = 1.022494888,
    //( 4.13643 * Math.pow(10,-16) * Math.pow($('#TempMash').val(),6) - 4.05998 * Math.pow(10,-13) * Math.pow($('#TempMash').val(),5) + 1.61536 * Math.pow(10,-10) * Math.pow($('#TempMash').val(),4) - 3.44854 * Math.pow(10,-8) * Math.pow($('#TempMash').val(),3) + 0.00000532769 * Math.pow($('#TempMash').val(),2) - 0.000292675 * $('#TempMash').val() + 1.00493),
    StrikeAdj = 1.025641026,
    //( 4.13643 * Math.pow(10,-16) * Math.pow($('#TempStrike').val(),6) - 4.05998 * Math.pow(10,-13) * Math.pow($('#TempStrike').val(),5) + 1.61536 * Math.pow(10,-10) * Math.pow($('#TempStrike').val(),4) - 3.44854 * Math.pow(10,-8) * Math.pow($('#TempStrike').val(),3) + 0.00000532769 * Math.pow($('#TempStrike').val(),2) - 0.000292675 * $('#TempStrike').val() + 1.00493),
    VolStrike = VolStart * StrikeAdj,
    LossHop = HBill * Habs,
    LossGrain = GBill * Gabs,
    VolMash = (VolStart + GBill * 0.08) * MashAdj,
    VolPre = (WaterTot - LossGrain) * 1.043841336,
    VolPost = (WaterTot - LossTot + LossTrub) * 1.043841336,
    VolChilled = ( VolPost / 1.043841336 ) - LossTrub,
    VolPackaged = VolChilled - LossFermTrub - ( DHop * Gabs ),
    GalH = 294.118334834 / (KettleID * KettleID),
    HTot = GalH * WaterTot,
    HStart = GalH * VolStart,
    HStrike = GalH * VolStrike,
    HMash = GalH * VolMash,
    HPre = GalH * VolPre,
    HChilled = GalH * VolChilled,
    MashThick = VolStart * 4 / GBill,
    VolMinSparge = Math.max(0,((WaterTot + GBill * 0.08) * MashAdj) - (PotSize - 0.01 )),
    HPost = GalH * VolPost,
    1Run = ( VolStart - LossGrain),
    2Run = ( ( VolPre / 1.043841336 ) * MashAdj) - 1Run,
    EBoil = (0.0058 * KettleID * KettleID) - (0.0009 * KettleID) + 0.0038;

    // console.log(VolStrike, WaterTot, MashThick, TempStrike);
    $('#WaterTot').text(WaterTot.toFixed(2));
    $('#VolStrike').text(VolStrike.toFixed(2));
    $('#LossBoil').text(LossBoil.toFixed(2));
    $('#LossHop').text(LossHop.toFixed(2));
    $('#LossGrain').text(LossGrain.toFixed(2));
    $('#LossTot').text(LossTot.toFixed(2));
    $('#LossFermTrub').text(LossFermTrub.toFixed(2));
    $('#VolStart').text(VolStart.toFixed(2));
    $('#VolMash').text(VolMash.toFixed(2));
    $('#VolPre').text(VolPre.toFixed(2));
    $('#VolPost').text(VolPost.toFixed(2));
    $('#TempStrike').text(TempStrike.toFixed(2));
    $('#MashAdj').text(MashAdj.toFixed(12));
    $('#Strikeadj').text(StrikeAdj.toFixed(12));
    $('#GalH').text(GalH.toFixed(3));
    $('#HTot').text(HTot.toFixed(2));
    $('#HStart').text(HStart.toFixed(2));
    $('#HStrike').text(HStrike.toFixed(2));
    $('#HMash').text(HMash.toFixed(2));
    $('#HPre').text(HPre.toFixed(2));
    $('#HPost').text(HPost.toFixed(2));
    $('#DHop').text(DHop.toFixed(2));
    $('#HChilled').text(HChilled.toFixed(2));
    $('#MashThick').text(MashThick.toFixed(2));
    $('#VolMinSparge').text(VolMinSparge.toFixed(2));
    $('#VolChilled').text(VolChilled.toFixed(2));
    $('#VolPackaged').text(VolPackaged.toFixed(2));
    $('#EBoil').text(EBoil.toFixed(2));
  }
  function validateField(field) {
    $field = $(field);
    if ($field.val().match(/^d*(.\d+)?/)) {
      $field.parents('div.control-group:first').removeClass('error');
      return true;
    }
    $field.parents('div.control-group:first').addClass('error');
    return false;
  }
  $(document).ready(function () {
    $('#calcForm').delegate('input[type=text]', 'change', function () {
      if (validateField(this)) {
        updateCalc();
      }
    }).submit(function (e) {
      e.defaultPrevented;
      updateCalc();
    });
    updateCalc();
    $('#GBill').focus();
  });
}) (jQuery);
<commit_msg>Update Testing4.js<commit_after>(function ($) {
  function allFieldsValid() {
    var fields = [
      'BatchVol',
      'GBill',
      'HBill',
      'DHop',
      'BoilTime',
      'BoilRate',
      'TempGrain',
      'TempMash',
      'VolSparge',
      'PotSize',
      'KettleID',
      'LossTrub',
      'LossFermTrub',
      'HChilled',
      'VolPackaged',
      'Gabs',
      'Habs',
      'EBoil',
    ];
    for (i = 0; i < fields.length; i++) {
      if (!$('#' + fields[i]).val().match(/^d*(.\d+)?/)) {
        return false;
      }
    }
    return true;
  }
  function updateCalc() {
    if (!allFieldsValid()) {
      return;
    }
    var BatchVol = parseFloat($('#BatchVol').val()),
    GBill = parseFloat($('#GBill').val()),
    HBill = parseFloat($('#HBill').val()),
    DHop = parseFloat($('#DHop').val()),
    BoilTime = parseFloat($('#BoilTime').val()),
    BoilRate = parseFloat($('#BoilRate').val()),
    TempGrain = parseFloat($('#TempGrain').val()),
    TempMash = parseFloat($('#TempMash').val()),
    VolSparge = parseFloat($('#VolSparge').val()),
    PotSize = parseFloat($('#PotSize').val()),
    KettleID = parseFloat($('#KettleID').val()),
    LossTrub = parseFloat($('#LossTrub').val()),
    Gabs = parseFloat($('#Gabs').val()),
    Habs = parseFloat($('#Habs').val()),
    MashAdj = parseFloat($('#MashAdj').val()),
    StrikeAdj = parseFloat($('#StrikeAdj').val()),
    LossFermTrub = parseFloat($('#LossFermTrub').val()),
    LossBoil = BoilTime * BoilRate / 60,
    LossHop = HBill * Habs,
    LossGrain = GBill * Gabs,
    LossTot = LossGrain + LossHop + LossBoil + LossTrub,
    WaterTot = BatchVol + LossTot,
    VolStart = (WaterTot - VolSparge),
    TempStrike = TempMash + (0.05 * GBill / VolStart) * (TempMash - TempGrain),
    MashAdj = 1.022494888,
    //( 4.13643 * Math.pow(10,-16) * Math.pow($('#TempMash').val(),6) - 4.05998 * Math.pow(10,-13) * Math.pow($('#TempMash').val(),5) + 1.61536 * Math.pow(10,-10) * Math.pow($('#TempMash').val(),4) - 3.44854 * Math.pow(10,-8) * Math.pow($('#TempMash').val(),3) + 0.00000532769 * Math.pow($('#TempMash').val(),2) - 0.000292675 * $('#TempMash').val() + 1.00493),
    StrikeAdj = 1.025641026,
    //( 4.13643 * Math.pow(10,-16) * Math.pow($('#TempStrike').val(),6) - 4.05998 * Math.pow(10,-13) * Math.pow($('#TempStrike').val(),5) + 1.61536 * Math.pow(10,-10) * Math.pow($('#TempStrike').val(),4) - 3.44854 * Math.pow(10,-8) * Math.pow($('#TempStrike').val(),3) + 0.00000532769 * Math.pow($('#TempStrike').val(),2) - 0.000292675 * $('#TempStrike').val() + 1.00493),
    VolStrike = VolStart * StrikeAdj,
    LossHop = HBill * Habs,
    LossGrain = GBill * Gabs,
    VolMash = (VolStart + GBill * 0.08) * MashAdj,
    VolPre = (WaterTot - LossGrain) * 1.043841336,
    VolPost = (WaterTot - LossTot + LossTrub) * 1.043841336,
    VolChilled = ( VolPost / 1.043841336 ) - LossTrub,
    VolPackaged = VolChilled - LossFermTrub - ( DHop * Gabs ),
    GalH = 294.118334834 / (KettleID * KettleID),
    HTot = GalH * WaterTot,
    HStart = GalH * VolStart,
    HStrike = GalH * VolStrike,
    HMash = GalH * VolMash,
    HPre = GalH * VolPre,
    HChilled = GalH * VolChilled,
    MashThick = VolStart * 4 / GBill,
    VolMinSparge = Math.max(0,((WaterTot + GBill * 0.08) * MashAdj) - (PotSize - 0.01 )),
    HPost = GalH * VolPost,
    1Run = ( VolStart - LossGrain),
    H1Run = 1Run * GalH,
    2Run = ( ( VolPre / 1.043841336 ) * MashAdj) - 1Run,
    H2Run = 2Run * GalH,
    EBoil = (0.0058 * KettleID * KettleID) - (0.0009 * KettleID) + 0.0038;

    // console.log(VolStrike, WaterTot, MashThick, TempStrike);
    $('#WaterTot').text(WaterTot.toFixed(2));
    $('#VolStrike').text(VolStrike.toFixed(2));
    $('#LossBoil').text(LossBoil.toFixed(2));
    $('#LossHop').text(LossHop.toFixed(2));
    $('#LossGrain').text(LossGrain.toFixed(2));
    $('#LossTot').text(LossTot.toFixed(2));
    $('#LossFermTrub').text(LossFermTrub.toFixed(2));
    $('#VolStart').text(VolStart.toFixed(2));
    $('#VolMash').text(VolMash.toFixed(2));
    $('#VolPre').text(VolPre.toFixed(2));
    $('#VolPost').text(VolPost.toFixed(2));
    $('#TempStrike').text(TempStrike.toFixed(2));
    $('#MashAdj').text(MashAdj.toFixed(12));
    $('#Strikeadj').text(StrikeAdj.toFixed(12));
    $('#GalH').text(GalH.toFixed(3));
    $('#HTot').text(HTot.toFixed(2));
    $('#HStart').text(HStart.toFixed(2));
    $('#HStrike').text(HStrike.toFixed(2));
    $('#HMash').text(HMash.toFixed(2));
    $('#HPre').text(HPre.toFixed(2));
    $('#HPost').text(HPost.toFixed(2));
    $('#DHop').text(DHop.toFixed(2));
    $('#HChilled').text(HChilled.toFixed(2));
    $('#MashThick').text(MashThick.toFixed(2));
    $('#VolMinSparge').text(VolMinSparge.toFixed(2));
    $('#VolChilled').text(VolChilled.toFixed(2));
    $('#VolPackaged').text(VolPackaged.toFixed(2));
    $('#1Run').text(1Run.toFixed(2));
    $('#H1Run').text(H1Run.toFixed(2));
    $('#H2Run').text(H2Run.toFixed(2));
    $('#2Run').text(2Run.toFixed(2));
    $('#EBoil').text(EBoil.toFixed(2));
  }
  function validateField(field) {
    $field = $(field);
    if ($field.val().match(/^d*(.\d+)?/)) {
      $field.parents('div.control-group:first').removeClass('error');
      return true;
    }
    $field.parents('div.control-group:first').addClass('error');
    return false;
  }
  $(document).ready(function () {
    $('#calcForm').delegate('input[type=text]', 'change', function () {
      if (validateField(this)) {
        updateCalc();
      }
    }).submit(function (e) {
      e.defaultPrevented;
      updateCalc();
    });
    updateCalc();
    $('#GBill').focus();
  });
}) (jQuery);
<|endoftext|>


<commit_before>import test from 'ava';
import { reducerTest } from 'redux-ava';

import reducer from '../../src/containers/councilmeeting/councilmeetingReducer';

const councilmeeting = { councilmeetingId: 1, instructorDeadlineDays: 8, studentDeadlineDays: 8, date: 'date' };
const councilmeetingEdited = { councilmeetingId: 1, instructorDeadlineDays: 10, studentDeadlineDays: 10, date: 'date' };
const councilmeeting2 = { councilmeetingId: 2, instructorDeadlineDays: 8, studentDeadlineDays: 8, date: 'date2' };

const councilmeetings = [councilmeeting, councilmeeting2];

const stateWithACouncilmeeting = [councilmeeting];
const stateWithCouncilmeetings = councilmeetings;
const stateWithEditedCouncilmeeting = [councilmeetingEdited];

test('get all success changes state correctly', reducerTest(
    reducer,
    [],
    {
        type: 'COUNCILMEETING_GET_ALL_SUCCESS',
        response: councilmeetings
    },
    stateWithCouncilmeetings,
));

test('save success changes state correctly', reducerTest(
    reducer,
    [],
    {
        type: 'COUNCILMEETING_SAVE_ONE_SUCCESS',
        response: councilmeeting
    },
    stateWithACouncilmeeting,
));

test('update success changes state correctly', reducerTest(
    reducer,
    [councilmeeting],
    {
        type: 'COUNCILMEETING_UPDATE_ONE_SUCCESS',
        response: councilmeetingEdited
    },
    stateWithEditedCouncilmeeting,
));

test('delete success changes state correctly', reducerTest(
    reducer,
    stateWithACouncilmeeting,
    {
        type: 'COUNCILMEETING_DELETE_ONE_SUCCESS',
        response: councilmeeting.councilmeetingId
    },
    []
));
<commit_msg>fix councilmeetingReducer test<commit_after>import test from 'ava';
import { reducerTest } from 'redux-ava';

import reducer from '../../src/containers/councilmeeting/councilmeetingReducer';

const councilmeeting = { councilmeetingId: 1, instructorDeadlineDays: 8, studentDeadlineDays: 8, date: 'date' };
const councilmeetingEdited = { councilmeetingId: 1, instructorDeadlineDays: 10, studentDeadlineDays: 10, date: 'date' };
const councilmeeting2 = { councilmeetingId: 2, instructorDeadlineDays: 8, studentDeadlineDays: 8, date: 'date2' };

const councilmeetings = [councilmeeting, councilmeeting2];

const stateWithACouncilmeeting = [councilmeeting];
const stateWithCouncilmeetings = councilmeetings;
const stateWithEditedCouncilmeeting = [councilmeetingEdited];

test('get all success changes state correctly', reducerTest(
    reducer,
    [],
    {
        type: 'COUNCILMEETING_GET_ALL_SUCCESS',
        response: councilmeetings
    },
    stateWithCouncilmeetings,
));

test('save success changes state correctly', reducerTest(
    reducer,
    [],
    {
        type: 'COUNCILMEETING_SAVE_ONE_SUCCESS',
        response: councilmeeting
    },
    stateWithACouncilmeeting,
));

test('update success changes state correctly', reducerTest(
    reducer,
    [councilmeeting],
    {
        type: 'COUNCILMEETING_UPDATE_ONE_SUCCESS',
        response: councilmeetingEdited
    },
    stateWithEditedCouncilmeeting,
));

test('delete success changes state correctly', reducerTest(
    reducer,
    stateWithACouncilmeeting,
    {
        type: 'COUNCILMEETING_DELETE_ONE_SUCCESS',
        response: { councilmeetingId: councilmeeting.councilmeetingId }
    },
    []
));
<|endoftext|>


<commit_before>'use strict';

var ObjectId = require("mongodb").ObjectId;
var should = require('should');
var helper = require("../../helper");
var Manager = require("../../../src/managers/garment-master-plan/booking-order-manager");
var manager = null;
var dataUtil =require("../../data-util/garment-master-plan/booking-order-data-util");
var validate = require("dl-models").validator.garmentMasterPlan.bookingOrder;

var ManagerPlan = require("../../../src/managers/garment-master-plan/master-plan-manager");
var managerPlan = null;
var dataUtilPlan =require("../../data-util/garment-master-plan/master-plan-data-util");
var validatePlan = require("dl-models").validator.garmentMasterPlan.masterPlan;

var moment = require('moment');

before('#00. connect db', function (done) {
    helper.getDb()
        .then(db => {
            manager = new Manager(db, {
                username: 'unit-test'
            });

            managerPlan = new ManagerPlan(db, {
                username: 'unit-test'
            });
            done();
        })
        .catch(e => {
            done(e);
        })
});

var createdId;
it("#01. should success when create new data", function (done) {
    dataUtil.getNewData()
        .then((data) => manager.create(data))
        .then((id) => {
            id.should.be.Object();
            createdId = id;
            done();
        })
        .catch((e) => {
            done(e);
        });
});

it("#01.5 should success when create new data masterPlan", function (done) {
    dataUtilPlan.getNewData()
        .then((data) => managerPlan.create(data))
        .then((id) => {
            id.should.be.Object();
            createdId = id;
            done();
        })
        .catch((e) => {
            done(e);
        });
});

var createdData;
it(`#02. should success when get created data with id`, function (done) {
    manager.getSingleById(createdId)
        .then((data) => {
            data.should.instanceof(Object);
            validate(data);
            createdData = data;
            done();
        })
        .catch((e) => {
            done(e);
        });
});

it("#03. should error when confirm created data without data items", function (done) {
    createdData.type='confirm';
    createdData.items = [{
        quantity: 0,
        deliveryDate : ''
    }];
        manager.update(createdData)
            .then((id) => {
                    done("should error when confirm created data without data items");
                })
                .catch((e) => {
                    e.name.should.equal("ValidationError");
                    e.should.have.property("errors");
                    e.errors.should.instanceof(Object);
                    e.errors.should.have.property("items");
                    done();
                });
});

it("#04. should error when confirm created data with deliveryDate items more than deliveryDate", function (done) {
    createdData.type='confirm';
    var today=new Date();
    createdData.deliveryDate=new Date(today.setDate(today.getDate() + 10));
    createdData.items[0].deliveryDate=new Date(createdData.deliveryDate.setDate(createdData.deliveryDate.getDate() + 10));
        manager.update(createdData)
            .then((id) => {
                    done("should error when confirm created data with deliveryDate items more than deliveryDate");
                })
                .catch((e) => {
                    e.name.should.equal("ValidationError");
                    e.should.have.property("errors");
                    e.errors.should.instanceof(Object);
                    e.errors.should.have.property("items");
                    done();
                });
});

it("#05. should error when confirm created data with deliveryDate items less than today", function (done) {
    createdData.type='confirm';
    var today=new Date();
    createdData.items[0].deliveryDate=new Date(today.setDate(today.getDate() - 10));
        manager.update(createdData)
            .then((id) => {
                    done("should error when confirm created data with deliveryDate items less than today");
                })
                .catch((e) => {
                    e.name.should.equal("ValidationError");
                    e.should.have.property("errors");
                    e.errors.should.instanceof(Object);
                    e.errors.should.have.property("items");
                    done();
                });
});

it("#06. should error when confirm created data with quantity items more than orderQuantity", function (done) {
    createdData.type='confirm';
    createdData.items[0].quantity=10000;
        manager.update(createdData)
            .then((id) => {
                    done("should error when confirm created data with quantity items more than orderQuantity");
                })
                .catch((e) => {
                    e.name.should.equal("ValidationError");
                    e.should.have.property("errors");
                    e.errors.should.instanceof(Object);
                    e.errors.should.have.property("items");
                    done();
                });
});<commit_msg>fix unit test<commit_after>'use strict';

var ObjectId = require("mongodb").ObjectId;
var should = require('should');
var helper = require("../../helper");
var Manager = require("../../../src/managers/garment-master-plan/booking-order-manager");
var manager = null;
var dataUtil =require("../../data-util/garment-master-plan/booking-order-data-util");
var validate = require("dl-models").validator.garmentMasterPlan.bookingOrder;

var ManagerPlan = require("../../../src/managers/garment-master-plan/master-plan-manager");
var managerPlan = null;
var dataUtilPlan =require("../../data-util/garment-master-plan/master-plan-data-util");
var validatePlan = require("dl-models").validator.garmentMasterPlan.masterPlan;

var moment = require('moment');

before('#00. connect db', function (done) {
    helper.getDb()
        .then(db => {
            manager = new Manager(db, {
                username: 'unit-test'
            });

            managerPlan = new ManagerPlan(db, {
                username: 'unit-test'
            });
            done();
        })
        .catch(e => {
            done(e);
        })
});

var createdId;
it("#01. should success when create new data", function (done) {
    dataUtil.getNewData()
        .then((data) => manager.create(data))
        .then((id) => {
            id.should.be.Object();
            createdId = id;
            done();
        })
        .catch((e) => {
            done(e);
        });
});

var createdData;
it(`#02. should success when get created data with id`, function (done) {
    manager.getSingleById(createdId)
        .then((data) => {
            data.should.instanceof(Object);
            validate(data);
            createdData = data;
            done();
        })
        .catch((e) => {
            done(e);
        });
});

it("#02.5 should success when create new data masterPlan", function (done) {
    dataUtilPlan.getNewData()
        .then((data) =>{
            data.bookingOrder=createdData;
            data.bookingOrderId = createdId;
            managerPlan.create(data)})
        .then((id) => {
            id.should.be.Object();
            done();
        })
        .catch((e) => {
            done(e);
        });
});

it("#03. should error when confirm created data without data items", function (done) {
    createdData.type='confirm';
    createdData.items = [{
        quantity: 0,
        deliveryDate : ''
    }];
        manager.update(createdData)
            .then((id) => {
                    done("should error when confirm created data without data items");
                })
                .catch((e) => {
                    e.name.should.equal("ValidationError");
                    e.should.have.property("errors");
                    e.errors.should.instanceof(Object);
                    e.errors.should.have.property("items");
                    done();
                });
});

it("#04. should error when confirm created data with deliveryDate items more than deliveryDate", function (done) {
    createdData.type='confirm';
    var today=new Date();
    createdData.deliveryDate=new Date(today.setDate(today.getDate() + 10));
    createdData.items[0].deliveryDate=new Date(createdData.deliveryDate.setDate(createdData.deliveryDate.getDate() + 10));
        manager.update(createdData)
            .then((id) => {
                    done("should error when confirm created data with deliveryDate items more than deliveryDate");
                })
                .catch((e) => {
                    e.name.should.equal("ValidationError");
                    e.should.have.property("errors");
                    e.errors.should.instanceof(Object);
                    e.errors.should.have.property("items");
                    done();
                });
});

it("#05. should error when confirm created data with deliveryDate items less than today", function (done) {
    createdData.type='confirm';
    var today=new Date();
    createdData.items[0].deliveryDate=new Date(today.setDate(today.getDate() - 10));
        manager.update(createdData)
            .then((id) => {
                    done("should error when confirm created data with deliveryDate items less than today");
                })
                .catch((e) => {
                    e.name.should.equal("ValidationError");
                    e.should.have.property("errors");
                    e.errors.should.instanceof(Object);
                    e.errors.should.have.property("items");
                    done();
                });
});

it("#06. should error when confirm created data with quantity items more than orderQuantity", function (done) {
    createdData.type='confirm';
    createdData.items[0].quantity=10000;
        manager.update(createdData)
            .then((id) => {
                    done("should error when confirm created data with quantity items more than orderQuantity");
                })
                .catch((e) => {
                    e.name.should.equal("ValidationError");
                    e.should.have.property("errors");
                    e.errors.should.instanceof(Object);
                    e.errors.should.have.property("items");
                    done();
                });
});<|endoftext|>


<commit_before>(function(){
var mod = angular.module('caspy.transaction', ['caspy.api', 'caspy.ui', 'generic']);

mod.factory('TransactionService', ['ResourceWrapper', 'caspyAPI',
    function(ResourceWrapper, caspyAPI) {
        function splitcmp(a, b) {
            if (0 < a.amount && 0 < b.amount)
                return 1/a.amount - 1/b.amount;
            return a.amount - b.amount;
        }

        function mapTransactions(transactions) {
            return transactions.map(function(xdata) {
                var xact = {
                    'date': xdata.date,
                    'description': xdata.description,
                    'splits': xdata.splits.sort(splitcmp)
                };
                return xact;
            });
        }

        return function(book_id) {
            var res = caspyAPI.get_resource('transaction', {'book_id': book_id});
            var rw = new ResourceWrapper(res, 'transaction_id');
            rw.orig_all = rw.all;
            rw.all = function() {
                return this.orig_all().then(mapTransactions);
            }
            return rw;
        };
    }]
);

mod.controller('TransactionController'
    ,['$injector'
    , '$routeParams'
    , 'ListControllerMixin'
    , 'TransactionService'
    , function($injector
             , $routeParams
             , ListControllerMixin
             , TransactionService
        ) {
        $injector.invoke(ListControllerMixin, this);
        var ref = this;
        this.book_id = $routeParams['book_id'];
        this.dataservice = TransactionService(this.book_id);
        this.assign('transactions', this.dataservice.all());
        this.pk = 'transaction_id';
    }]
);

mod.controller('TransactionEditController'
    ,['$scope', 'focus',
    function($scope, focus) {
        var ctrl = this;
        this.splits = function(splits) {
            if (arguments.length)
                $scope.transaction.splits = splits;
            return $scope.transaction.splits;
        };
        this.addSplit = function addSplit(amount) {
            var splitObj = {
                     'amount': amount
                    ,'status': 'n'
                    ,'number': ''
                    ,'description': ''
                };
            splitObj.auto = true;
            this.splits().push(splitObj);
            if (arguments.length)
                focus('split.auto');
        };

        function nonzero(split) {
            return split.amount || split.auto;
        }

        this.onSplitChange = function(split) {
            split.auto = false;
            var total = 0;
            var auto;
            var splits = this.splits(this.splits().filter(nonzero));
            splits.forEach(function(s) {
                if (s.auto)
                    auto = s;
                else
                    total += +s.amount;
            });
            if (0 != total) {
                if (auto)
                    auto.amount = -total;
                else
                    this.addSplit(-total);
            }
        };
        this.onTransactionChange = function() {
            if (typeof this.splits() === 'undefined')
                this.splits([]);
            if (0 == this.splits().length)
                this.addSplit();
            focus("cspFocus == 'date'");
        }
        $scope.$watch('transaction', function() {
            if ($scope.transaction)
                ctrl.onTransactionChange();
        });
    }]
);

mod.directive('cspTransactionEdit', function() {
    return {
          scope: {
            transaction: '='
          }
        , controller: 'TransactionEditController'
        , templateUrl: 'partials/transaction/transaction-edit.html'
    };
});

})();
<commit_msg>Auto-remove the auto split<commit_after>(function(){
var mod = angular.module('caspy.transaction', ['caspy.api', 'caspy.ui', 'generic']);

mod.factory('TransactionService', ['ResourceWrapper', 'caspyAPI',
    function(ResourceWrapper, caspyAPI) {
        function splitcmp(a, b) {
            if (0 < a.amount && 0 < b.amount)
                return 1/a.amount - 1/b.amount;
            return a.amount - b.amount;
        }

        function mapTransactions(transactions) {
            return transactions.map(function(xdata) {
                var xact = {
                    'date': xdata.date,
                    'description': xdata.description,
                    'splits': xdata.splits.sort(splitcmp)
                };
                return xact;
            });
        }

        return function(book_id) {
            var res = caspyAPI.get_resource('transaction', {'book_id': book_id});
            var rw = new ResourceWrapper(res, 'transaction_id');
            rw.orig_all = rw.all;
            rw.all = function() {
                return this.orig_all().then(mapTransactions);
            }
            return rw;
        };
    }]
);

mod.controller('TransactionController'
    ,['$injector'
    , '$routeParams'
    , 'ListControllerMixin'
    , 'TransactionService'
    , function($injector
             , $routeParams
             , ListControllerMixin
             , TransactionService
        ) {
        $injector.invoke(ListControllerMixin, this);
        var ref = this;
        this.book_id = $routeParams['book_id'];
        this.dataservice = TransactionService(this.book_id);
        this.assign('transactions', this.dataservice.all());
        this.pk = 'transaction_id';
    }]
);

mod.controller('TransactionEditController'
    ,['$scope', 'focus',
    function($scope, focus) {
        var ctrl = this;
        this.splits = function(splits) {
            if (arguments.length)
                $scope.transaction.splits = splits;
            return $scope.transaction.splits;
        };
        this.addSplit = function addSplit(amount) {
            var splitObj = {
                     'amount': amount
                    ,'status': 'n'
                    ,'number': ''
                    ,'description': ''
                };
            splitObj.auto = true;
            this.splits().push(splitObj);
            if (arguments.length)
                focus('split.auto');
        };

        function nonzero(split) {
            return split.amount || split.auto;
        }

        this.onSplitChange = function(split) {
            split.auto = false;
            var total = 0;
            var auto;
            var splits = this.splits(this.splits().filter(nonzero));
            splits.forEach(function(s, i) {
                if (s.auto)
                    auto = i;
                else
                    total += +s.amount;
            });
            if (0 != total) {
                if (typeof auto !== 'undefined')
                    splits[auto].amount = -total;
                else
                    this.addSplit(-total);
            } else {
                splits.splice(auto, 1);
            }
        };
        this.onTransactionChange = function() {
            if (typeof this.splits() === 'undefined')
                this.splits([]);
            if (0 == this.splits().length)
                this.addSplit();
            focus("cspFocus == 'date'");
        }
        $scope.$watch('transaction', function() {
            if ($scope.transaction)
                ctrl.onTransactionChange();
        });
    }]
);

mod.directive('cspTransactionEdit', function() {
    return {
          scope: {
            transaction: '='
          }
        , controller: 'TransactionEditController'
        , templateUrl: 'partials/transaction/transaction-edit.html'
    };
});

})();
<|endoftext|>


<commit_before>
import { Immutable } from '../../src/libs/dependencies';
import extractDecoratorProperties from '../../src/helpers/extract-decorator-properties';

describe('extractDecoratorProperties', () => {

	function MockElement(attributes){
		this.attributes = attributes;
	}

	MockElement.prototype = {
		// simulate removing of removeAttribute
		removeAttribute : function(attribute) {
			for(let attr in this.attributes){
				if(!this.attributes.hasOwnProperty(attr)) continue;
				if(this.attributes[attr].name === attribute){
					delete this.attributes[attr];
				}
			}
		}
	};

	it('it should return objectlist of dom attributes that matched of @view.bind', () => {

		let MockDomNode = new MockElement({
			0: { name: '@view.bind.b', value: 'baz' },
			1: { name: 'class', value: 'foo' },
			2: { name: '@view.bind.x', value: 'bar' },
		});

		let viewAttributeList = extractDecoratorProperties(MockDomNode, '@view.bind', true);

		// positiv test
		viewAttributeList.should.have.propertyByPath('x').eql('bar');
		viewAttributeList.should.have.propertyByPath('b').eql('baz');
		// negativ test
		viewAttributeList.should.not.have.propertyByPath('class');

		// the third argument for getDomAttributes removes the dom attribute
		Object.keys(MockDomNode.attributes).should.have.length(1);
		MockDomNode.attributes.should.have.propertyByPath(1, 'name').eql('class');

		let regularAttributeList = extractDecoratorProperties(MockDomNode);
		regularAttributeList.should.have.propertyByPath('class').eql('foo');

	});

	it('it should return objectlist of dom attributes that matched of @on', () => {

		let MockDomNode = new MockElement({
			0: { name: '@on(click)', value: 'clickFunction()' },
			1: { name: 'id', value: 'bazi' },
			2: { name: '@on(mousedown)', value: 'mousedownFunction()' },
		});

		let viewAttributeList = extractDecoratorProperties(MockDomNode, '@on', true);

		// positiv test
		viewAttributeList.should.have.propertyByPath('click').eql('clickFunction()');
		viewAttributeList.should.have.propertyByPath('mousedown').eql('mousedownFunction()');
		// negativ test
		viewAttributeList.should.not.have.propertyByPath('id');

		// the third argument for getDomAttributes removes the dom attribute
		Object.keys(MockDomNode.attributes).should.have.length(1);
		MockDomNode.attributes.should.have.propertyByPath(1, 'name').eql('id');

		let regularAttributeList = extractDecoratorProperties(MockDomNode);
		regularAttributeList.should.have.propertyByPath('id').eql('bazi');

	});

});
<commit_msg>Removed immutable lib<commit_after>
import extractDecoratorProperties from '../../src/helpers/extract-decorator-properties';

describe('extractDecoratorProperties', () => {

	function MockElement(attributes){
		this.attributes = attributes;
	}

	MockElement.prototype = {
		// simulate removing of removeAttribute
		removeAttribute : function(attribute) {
			for(let attr in this.attributes){
				if(!this.attributes.hasOwnProperty(attr)) continue;
				if(this.attributes[attr].name === attribute){
					delete this.attributes[attr];
				}
			}
		}
	};

	it('it should return objectlist of dom attributes that matched of @view.bind', () => {

		let MockDomNode = new MockElement({
			0: { name: '@view.bind.b', value: 'baz' },
			1: { name: 'class', value: 'foo' },
			2: { name: '@view.bind.x', value: 'bar' },
		});

		let viewAttributeList = extractDecoratorProperties(MockDomNode, '@view.bind', true);

		// positiv test
		viewAttributeList.should.have.propertyByPath('x').eql('bar');
		viewAttributeList.should.have.propertyByPath('b').eql('baz');
		// negativ test
		viewAttributeList.should.not.have.propertyByPath('class');

		// the third argument for getDomAttributes removes the dom attribute
		Object.keys(MockDomNode.attributes).should.have.length(1);
		MockDomNode.attributes.should.have.propertyByPath(1, 'name').eql('class');

		let regularAttributeList = extractDecoratorProperties(MockDomNode);
		regularAttributeList.should.have.propertyByPath('class').eql('foo');

	});

	it('it should return objectlist of dom attributes that matched of @on', () => {

		let MockDomNode = new MockElement({
			0: { name: '@on(click)', value: 'clickFunction()' },
			1: { name: 'id', value: 'bazi' },
			2: { name: '@on(mousedown)', value: 'mousedownFunction()' },
		});

		let viewAttributeList = extractDecoratorProperties(MockDomNode, '@on', true);

		// positiv test
		viewAttributeList.should.have.propertyByPath('click').eql('clickFunction()');
		viewAttributeList.should.have.propertyByPath('mousedown').eql('mousedownFunction()');
		// negativ test
		viewAttributeList.should.not.have.propertyByPath('id');

		// the third argument for getDomAttributes removes the dom attribute
		Object.keys(MockDomNode.attributes).should.have.length(1);
		MockDomNode.attributes.should.have.propertyByPath(1, 'name').eql('id');

		let regularAttributeList = extractDecoratorProperties(MockDomNode);
		regularAttributeList.should.have.propertyByPath('id').eql('bazi');

	});

});
<|endoftext|>


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

/**
 * Generates the tsconfig.json file for typescript compilation.
 *
 * See https://github.com/phetsims/chipper/issues/63
 *
 * @author Sam Reid (PhET Interactive Simulations)
 */

// modules
const fixEOL = require( './fixEOL' );
const grunt = require( 'grunt' );

/**
 * @param {string} repo
 *
 * @returns {Promise}
 */
module.exports = async function( repo ) {

  const string = `{
  "extends": "../chipper/tsconfig-core.json",
  "references": [
    {
      "path": "../chipper/tsconfig/joist"
    }
  ],
  "include": [
    "js/**/*",
    "images/**/*",
    "sounds/**/*",
    "mipmaps/**/*",
    "../chipper/phet-types.d.ts"
  ],
  "exclude": [
    "node_modules",
    "build"
  ]
}`;

  const outputFile = `../${repo}/tsconfig.json`;

  // Write to the repository's root directory.
  grunt.file.write( outputFile, fixEOL( string ) );
};<commit_msg>update copyright dates from daily grunt work<commit_after>// Copyright 2021, University of Colorado Boulder

/**
 * Generates the tsconfig.json file for typescript compilation.
 *
 * See https://github.com/phetsims/chipper/issues/63
 *
 * @author Sam Reid (PhET Interactive Simulations)
 */

// modules
const fixEOL = require( './fixEOL' );
const grunt = require( 'grunt' );

/**
 * @param {string} repo
 *
 * @returns {Promise}
 */
module.exports = async function( repo ) {

  const string = `{
  "extends": "../chipper/tsconfig-core.json",
  "references": [
    {
      "path": "../chipper/tsconfig/joist"
    }
  ],
  "include": [
    "js/**/*",
    "images/**/*",
    "sounds/**/*",
    "mipmaps/**/*",
    "../chipper/phet-types.d.ts"
  ],
  "exclude": [
    "node_modules",
    "build"
  ]
}`;

  const outputFile = `../${repo}/tsconfig.json`;

  // Write to the repository's root directory.
  grunt.file.write( outputFile, fixEOL( string ) );
};<|endoftext|>


<commit_before>/**
 * @file This file is an importable module to handle and communicate to the neural network (web) worker body.
 *
 */

import * as ValueMax from "../ValueMax.js";
//import * as Net from "./Net.js";

export { InitProgress, InitProgressAll, PendingPromiseInfo, Base };

/**
 * Aggregate initialization progress of one web worker.
 * Including: download tensorflow and neural network library, download neural network weights, parse neural network weights.
 */
class InitProgress extends ValueMax.Percentage.Aggregate {
  constructor() {
    let children = [
      new ValueMax.Percentage.Concrete(), // Increased when downloading tensorflow and neural network library.
      new ValueMax.Percentage.Concrete(), // Increased when downloading neural network weights.
      new ValueMax.Percentage.Concrete(), // Increased when parsing neural network weights.
    ];

    super( children );
    [ this.libraryDownload, this.weightsDownload, this.weightsParse ] = children;
  }
}

/**
 * Aggregate initialization progress of two workers.
 */
class InitProgressAll extends ValueMax.Percentage.Aggregate {
  constructor() {
    let children = [
      new InitProgress(), // Increased when web worker 1 initializing.
      new InitProgress(), // Increased when web worker 2 initializing.
    ];

    super( children );
    [ this.worker1, this.worker2 ] = children;
  }
}

/**
 * Hold a processing's id, promise, resolve (fulfilling function object), reject (rejecting function object).
 *
 * @member {number}   workerId     The array index of the worker owns this processing.
 * @member {number}   processingId The id of the processing.
 * @member {Promise}  promise      The pending promise for the processing.
 * @member {function} resolve      The fulfilling function object of the pending promise for the processing.
 * @member {function} reject       The rejecting function object of the pending promise for the processing.
 */
class PromiseResolveReject {

  constructor( workerId, processingId ) {
    this.workerId = workerId;
    this.processingId = processingId;

    this.promise = new Promise( ( resolve, reject ) => {
      this.resolve = resolve;
      this.reject = reject;
    });
  }

}

/**
 * Hold two PromiseResolveReject.
 *
 * @member {PromiseResolveReject}   process The promise for reporting processTensor() done.
 * @member {PromiseResolveReject}   relay   The promise for reporting scaledSourceImageData is received from this web worker.
 */
class ProcessRelayPromises {

  constructor( workerId, processingId ) {
    this.process = new PromiseResolveReject( workerId, processingId );
    this.relay = new PromiseResolveReject( workerId, processingId );
  }

}

/**
 * Hold the worker and its related promise map. It is a wrapper of a neural network web worker for handling easily.
 *
 * @member {number} workerId              The array index of this worker proxy.
 * @member {Worker} worker                The worker.
 * @member {Map}    pendingPromiseInfoMap The map for promise of the unhandled processing.
 */
class Base {

  /**
   * Initialize this worker proxy. It will create one web worker and inform it to create one neural network.
   *
   * @param {number} workerId
   *   This id of this worker proxy (and web worker). This is the array index in the parent container (i.e. WorkerController).
   *
   * @param {string} tensorflowJsURL
   *   The URL of tensorflow javascript library. Every worker will load the library from the URL.
   *
   * @param {Net.Config} neuralNetConfig
   *   The configuration of the neural network which will be created by this web worker.
   *
   * @param {string} weightsURL
   *   The URL of neural network weights. Every worker will load weights from the URL to initialize one neural network.
   *
   * @param {InitProgress} initProgress
   *   This worker proxy will modify theInitProgress to report its web worker's initialization progress.
   */
  init( workerId, tensorflowJsURL, neuralNetConfig, weightsURL, initProgress ) {
    this.workerId = workerId;
    this.tensorflowJsURL = tensorflowJsURL;
    this.neuralNetConfig = neuralNetConfig;
    this.weightsURL = weightsURL;
    this.initProgress = initProgress;

    // Every worker has a result pending promise map. The key of the map is processing id. The value of the map is a ProcessRelayPromises.
    this.processRelayPromisesMap = new Map();

//!!! ...unfinished...

    // Assume the main (i.e. body) javascript file of neural network web worker is a sibling file (i.e. inside the same folder) of this module file.
    this.workerURL = new URL( import.meta.url, "WorkerBody.js" );

    // Should not use "module" type worker, otherwise the worker can not use importScripts() to load tensorflow library.
    //this.workerOptions = { type: "module" }; // So that the worker script could use import statement.
    this.workerOptions = null;

    let worker = this.worker = new Worker( this.workerURL, this.workerOptions );
    worker.onmessage = Base.onmessage_fromWorker.bind( this ); // Register callback from the web worker.

    // Worker Initialization message.
    let message = {
      command: "init",
      workerId: workerId,
      tensorflowJsURL: tensorflowJsURL,
      neuralNetConfig: neuralNetConfig,
      weightsURL: weightsURL
    };

    worker.postMessage( message );  // Inform the worker to initialize.
  }

  /**
   *
   */
  disposeWorker() {
    {
      let message = { command: "disposeWorker" };
      this.worker.postMessage( message );
      this.worker  = null;
    }

    this.initProgress = null;
    this.nextWorkerProxy = null;
  }

  /**
   * @param {number} processingId
   *   The processing id for distinguishing different processing request and result.
   *
   * @param {ImageData} sourceImageData
   *   The source image data to be processed. Its shape should be [ height, width, channel ] = [ this.neuralNet.sourceImageHeightWidth[ 0 ],
   * this.neuralNet.sourceImageHeightWidth[ 1 ], this.neuralNet.config.sourceChannelCount ]. This usually is called for the first web worker
   * in chain. The web worker will tansfer back a scaled typed-array. The scaled typed-array should be used to call the next web worker's
   * processTypedArrayAsync().
   *
   * @return {Promise}
   *   Return a promise which will be resolved when all worker pending promises of the same processingId are resolved. The promise
   * resolved with an array of typed-array. Every type-array comes from the output tensor of one worker's neural network.
   */
  async imageData_transferBack_processTensor_async( processingId, sourceImageData ) {

    // Prepare promises and their function object (resolve and reject) in a map so that the promises can be found and resolved when processing is done.
    //
    // The processRelayPromises.relay.promise will be await by outter (i.e. WorkerController) to transfer source typed-array to every web worker serially.
    // The processRelayPromises.process.promise will be returned as the result of this processTensor().
    let processRelayPromises = new ProcessRelayPromises( this.workerId, processingId );
    this.processRelayPromisesMap.set( processingId, processRelayPromises );

    // Transfer (not copy) the source image data to this (worker proxy owned) web worker.
    let message = { command: "imageData_transferBack_processTensor", processingId: processingId, sourceImageData: sourceImageData };
    this.worker.postMessage( message, [ message.sourceImageData.data.buffer ] );
    // Now, sourceImageData.data.buffer has become invalid because it is transferred (not copied) to web worker.

    return processRelayPromises.process.promise;
  }

  /**
   * @param {number} processingId
   *   The processing id for distinguishing different processing request and result.
   *
   * @param {Float32Array} sourceTypedArray
   *   The source typed-data to be processed. Its shape should be [ height, width, channel ] = [ this.neuralNet.sourceImageHeightWidth[ 0 ],
   * this.neuralNet.sourceImageHeightWidth[ 1 ], this.neuralNet.config.sourceChannelCount ]. This usually is come from the previous web work
   * by on_transferBackSourceTypedArray(). This usually is called for the second (and after) web worker in chain. The web worker will
   * tansfer back this scaled typed-array. The scaled typed-array should continuously be used to call the next web worker's
   * processTypedArrayAsync().
   *
   * @return {Promise}
   *   Return a promise which will be resolved when all worker pending promises of the same processingId are resolved. The promise
   * resolved with an array of typed-array. Every type-array comes from the output tensor of one worker's neural network.
   */
  async typedArray_transferBack_processTensor_async( processingId, sourceTypedArray ) {

    // Prepare promises and their function object (resolve and reject) in a map so that the promises can be found and resolved when processing is done.
    //
    // The processRelayPromises.relay.promise will be await by outter (i.e. WorkerController) to transfer source typed-array to every web worker serially.
    // The processRelayPromises.process.promise will be returned as the result of this processTensor().
    let processRelayPromises = new ProcessRelayPromises( this.workerId, processingId );
    this.processRelayPromisesMap.set( processingId, processRelayPromises );

    // Transfer (not copy) the source typed-array to this (worker proxy owned) web worker.
    let message = { command: "typedArray_transferBack_processTensor", processingId: processingId, sourceTypedArray: sourceTypedArray };
    this.worker.postMessage( message, [ message.sourceTypedArray.buffer ] );
    // Now, sourceTypedArray.buffer has become invalid because it is transferred (not copied) to web worker.

    return processRelayPromises.process.promise;
  }

  /**
   * Handle messages from the progress of loading library of web workers.
   */
  initLibraryProgress_onReport( workerId ) {
//!!! ...unfinished...
  }

  /**
   * Handle messages from the progress of loading neural network of web workers.
   */
  initNeuralNetProgress_onReport( workerId ) {
//!!! ...unfinished...
  }

  /**
   * Called when the scaled source typed-array from web worker is received.
   */
  on_transferBackSourceTypedArray( workerId, processingId, sourceTypedArray ) {

    if ( workerId != this.workerId )
      return; // Ignore if wrong worker id.

    let processRelayPromises = this.processRelayPromisesMap.get( processingId );
    if ( !processRelayPromises )
      return; // Ignore if processing id does not existed. (e.g. already handled)

    processRelayPromises.relay.resolve( sourceTypedArray ); // This will be received by WorkerController.

    // Here, the processRelayPromises should not be removed from processRelayPromisesMap.
    // It will be removed when processTensor() done completely (i.e. inside on_processTensorResult()).
  }

  /**
   * Called when the processed tensor from web worker is received.
   *
   * @param {number} workerId
   *   The id of the worker which sent the result back.
   *
   * @param {number} processingId
   *   The processing id of the result.
   *
   * @param {TypedArray} resultTypedArray
   *   The result of the returned processing. It is the downloaded data of the result tensor.
   */
  on_processTensorResult( workerId, processingId, resultTypedArray ) {

    if ( workerId != this.workerId )
      return; // Ignore if wrong worker id.

    let processRelayPromises = this.processRelayPromisesMap.get( processingId );
    if ( !processRelayPromises )
      return; // Discard result with non-existed processing id. (e.g. already handled old processing result)

    processRelayPromises.process.resolve( resultTypedArray );

//!!! ...unfinished... When will fail?
    //processRelayPromises.reject();

//!!! ...unfinished... Whether should the older (i.e. smaller) processingId be cleared from map? (Could the processing be out of order?)

    this.processRelayPromisesMap.delete( processingId ); // Clear the info entry of handled processing result.
  }

  /**
   * Dispatch messages come from the owned web worker.
   *
   * @param {Base} this
   *   Should be binded to this object.
   */
  static onmessage_fromWorker( e ) {
    let message = e.data;

    switch ( message.command ) {
      case "initLibraryProgressReport": //{ command: "initLibraryProgressReport", workerId, ,  };
//!!! ...unfinished...
        //this.initLibraryProgress_onReport( message.workerId, ,  );
        break;

      case "initNeuralNetProgressReport": //{ command: "initNeuralNetProgressReport", workerId, ,  };
//!!! ...unfinished...
        //this.initNeuralNetProgress_onReport( message.workerId, ,  );
        break;

      case "transferBackSourceTypedArray": //{ command: "transferBackSourceTypedArray", workerId, processingId, sourceTypedArray };
        this.on_transferBackSourceTypedArray( message.workerId, message.processingId, message.sourceTypedArray );
        break;

      case "processTensorResult": //{ command: "processTensorResult", workerId, processingId, resultTypedArray };
        this.on_processTensorResult( message.workerId, message.processingId, message.resultTypedArray );
        break;

    }
  }

}
<commit_msg>Update WorkerProxy.js<commit_after>/**
 * @file This file is an importable module to handle and communicate to the neural network (web) worker body.
 *
 */

import * as ValueMax from "../ValueMax.js";
//import * as Net from "./Net.js";

export { InitProgress, InitProgressAll, PendingPromiseInfo, Base };

/**
 * Aggregate initialization progress of one web worker.
 * Including: download tensorflow and neural network library, download neural network weights, parse neural network weights.
 */
class InitProgress extends ValueMax.Percentage.Aggregate {
  constructor() {
    let children = [
      new ValueMax.Percentage.Concrete(), // Increased when downloading tensorflow and neural network library.
      new ValueMax.Percentage.Concrete(), // Increased when downloading neural network weights.
      new ValueMax.Percentage.Concrete(), // Increased when parsing neural network weights.
    ];

    super( children );
    [ this.libraryDownload, this.weightsDownload, this.weightsParse ] = children;
  }
}

/**
 * Aggregate initialization progress of two workers.
 */
class InitProgressAll extends ValueMax.Percentage.Aggregate {
  constructor() {
    let children = [
      new InitProgress(), // Increased when web worker 1 initializing.
      new InitProgress(), // Increased when web worker 2 initializing.
    ];

    super( children );
    [ this.worker1, this.worker2 ] = children;
  }
}

/**
 * Hold a processing's id, promise, resolve (fulfilling function object), reject (rejecting function object).
 *
 * @member {number}   workerId     The array index of the worker owns this processing.
 * @member {number}   processingId The id of the processing.
 * @member {Promise}  promise      The pending promise for the processing.
 * @member {function} resolve      The fulfilling function object of the pending promise for the processing.
 * @member {function} reject       The rejecting function object of the pending promise for the processing.
 */
class PromiseResolveReject {

  constructor( workerId, processingId ) {
    this.workerId = workerId;
    this.processingId = processingId;

    this.promise = new Promise( ( resolve, reject ) => {
      this.resolve = resolve;
      this.reject = reject;
    });
  }

}

/**
 * Hold two PromiseResolveReject.
 *
 * @member {PromiseResolveReject}   process The promise for reporting processTensor() done.
 * @member {PromiseResolveReject}   relay   The promise for reporting scaledSourceImageData is received from this web worker.
 */
class ProcessRelayPromises {

  constructor( workerId, processingId ) {
    this.process = new PromiseResolveReject( workerId, processingId );
    this.relay = new PromiseResolveReject( workerId, processingId );
  }

}

/**
 * Hold the worker and its related promise map. It is a wrapper of a neural network web worker for handling easily.
 *
 * @member {number} workerId              The array index of this worker proxy.
 * @member {Worker} worker                The worker.
 * @member {Map}    pendingPromiseInfoMap The map for promise of the unhandled processing.
 */
class Base {

  /**
   * Initialize this worker proxy. It will create one web worker and inform it to create one neural network.
   *
   * @param {number} workerId
   *   This id of this worker proxy (and web worker). This is the array index in the parent container (i.e. WorkerController).
   *
   * @param {string} tensorflowJsURL
   *   The URL of tensorflow javascript library. Every worker will load the library from the URL.
   *
   * @param {Net.Config} neuralNetConfig
   *   The configuration of the neural network which will be created by this web worker.
   *
   * @param {string} weightsURL
   *   The URL of neural network weights. Every worker will load weights from the URL to initialize one neural network.
   *
   * @param {InitProgress} initProgress
   *   This worker proxy will modify theInitProgress to report its web worker's initialization progress.
   */
  init( workerId, tensorflowJsURL, neuralNetConfig, weightsURL, initProgress ) {
    this.workerId = workerId;
    this.tensorflowJsURL = tensorflowJsURL;
    this.neuralNetConfig = neuralNetConfig;
    this.weightsURL = weightsURL;
    this.initProgress = initProgress;

    // Every worker has a result pending promise map. The key of the map is processing id. The value of the map is a ProcessRelayPromises.
    this.processRelayPromisesMap = new Map();

//!!! ...unfinished...

    // Assume the main (i.e. body) javascript file of neural network web worker is a sibling file (i.e. inside the same folder) of this module file.
    this.workerURL = new URL( import.meta.url, "WorkerBody.js" );

    // Should not use "module" type worker, otherwise the worker can not use importScripts() to load tensorflow library.
    //this.workerOptions = { type: "module" }; // So that the worker script could use import statement.
    this.workerOptions = null;

    let worker = this.worker = new Worker( this.workerURL, this.workerOptions );
    worker.onmessage = Base.onmessage_fromWorker.bind( this ); // Register callback from the web worker.

    // Worker Initialization message.
    let message = {
      command: "init",
      workerId: workerId,
      tensorflowJsURL: tensorflowJsURL,
      neuralNetConfig: neuralNetConfig,
      weightsURL: weightsURL
    };

    worker.postMessage( message );  // Inform the worker to initialize.
  }

  /**
   *
   */
  disposeWorker() {
    {
      let message = { command: "disposeWorker" };
      this.worker.postMessage( message );
      this.worker  = null;
    }

    this.initProgress = null;
    this.nextWorkerProxy = null;
  }

  /**
   * @param {number} processingId
   *   The processing id for distinguishing different processing request and result.
   *
   * @param {ImageData} sourceImageData
   *   The source image data to be processed. Its shape should be [ height, width, channel ] = [ this.neuralNet.sourceImageHeightWidth[ 0 ],
   * this.neuralNet.sourceImageHeightWidth[ 1 ], this.neuralNet.config.sourceChannelCount ]. This usually is called for the first web worker
   * in chain. The web worker will tansfer back a scaled typed-array. The scaled typed-array should be used to call the next web worker's
   * typedArray_transferBack_processTensor_async().
   *
   * @return {Promise}
   *   Return a promise which will be resolved when this (WorkerProxy owned) web worker's neural network computing done. It resolved with
   * a typed-array which comes from the output tensor of the web worker's neural network.
   */
  async imageData_transferBack_processTensor_async( processingId, sourceImageData ) {

    // Prepare promises and their function object (resolve and reject) in a map so that the promises can be found and resolved when processing is done.
    //
    // The processRelayPromises.relay.promise will be await by outter (i.e. WorkerController) to transfer source typed-array to every web worker serially.
    // The processRelayPromises.process.promise will be returned as the result of this processTensor().
    let processRelayPromises = new ProcessRelayPromises( this.workerId, processingId );
    this.processRelayPromisesMap.set( processingId, processRelayPromises );

    // Transfer (not copy) the source image data to this (worker proxy owned) web worker.
    let message = { command: "imageData_transferBack_processTensor", processingId: processingId, sourceImageData: sourceImageData };
    this.worker.postMessage( message, [ message.sourceImageData.data.buffer ] );
    // Now, sourceImageData.data.buffer has become invalid because it is transferred (not copied) to web worker.

    return processRelayPromises.process.promise;
  }

  /**
   * @param {number} processingId
   *   The processing id for distinguishing different processing request and result.
   *
   * @param {Float32Array} sourceTypedArray
   *   The source typed-data to be processed. Its shape should be [ height, width, channel ] = [ this.neuralNet.sourceImageHeightWidth[ 0 ],
   * this.neuralNet.sourceImageHeightWidth[ 1 ], this.neuralNet.config.sourceChannelCount ]. This usually is come from the previous web work
   * by on_transferBackSourceTypedArray(). This usually is called for the second (and after) web worker in chain. The web worker will
   * tansfer back this scaled typed-array. The scaled typed-array should continuously be used to call the next web worker's
   * typedArray_transferBack_processTensor_async().
   *
   * @return {Promise}
   *   Return a promise which will be resolved when this (WorkerProxy owned) web worker's neural network computing done. It resolved with
   * a typed-array which comes from the output tensor of the web worker's neural network.
   */
  async typedArray_transferBack_processTensor_async( processingId, sourceTypedArray ) {

    // Prepare promises and their function object (resolve and reject) in a map so that the promises can be found and resolved when processing is done.
    //
    // The processRelayPromises.relay.promise will be await by outter (i.e. WorkerController) to transfer source typed-array to every web worker serially.
    // The processRelayPromises.process.promise will be returned as the result of this processTensor().
    let processRelayPromises = new ProcessRelayPromises( this.workerId, processingId );
    this.processRelayPromisesMap.set( processingId, processRelayPromises );

    // Transfer (not copy) the source typed-array to this (worker proxy owned) web worker.
    let message = { command: "typedArray_transferBack_processTensor", processingId: processingId, sourceTypedArray: sourceTypedArray };
    this.worker.postMessage( message, [ message.sourceTypedArray.buffer ] );
    // Now, sourceTypedArray.buffer has become invalid because it is transferred (not copied) to web worker.

    return processRelayPromises.process.promise;
  }

  /**
   * This will not transfer source back to WorkerProxy. This is different from typedArray_transferBack_processTensor_async().
   *
   * @param {number} processingId
   *   The processing id for distinguishing different processing request and result.
   *
   * @param {Float32Array} sourceTypedArray
   *   The source typed-data to be processed. Its shape should be [ height, width, channel ] = [ this.neuralNet.sourceImageHeightWidth[ 0 ],
   * this.neuralNet.sourceImageHeightWidth[ 1 ], this.neuralNet.config.sourceChannelCount ]. This usually is come from the previous web work
   * by on_transferBackSourceTypedArray().
   *
   * @return {Promise}
   *   Return a promise which will be resolved when this (WorkerProxy owned) web worker's neural network computing done. It resolved with
   * a typed-array which comes from the output tensor of the web worker's neural network.
   */
  async typedArray_processTensor_async( processingId, sourceTypedArray ) {

    // Prepare promises and their function object (resolve and reject) in a map so that the promises can be found and resolved when processing is done.
    //
    // The processRelayPromises.relay.promise will not be used because the source typed-array will not be transferred back here.
    // The processRelayPromises.process.promise will be returned as the result of this processTensor().
    let processRelayPromises = new ProcessRelayPromises( this.workerId, processingId );
    this.processRelayPromisesMap.set( processingId, processRelayPromises );

    // Copy (not transfer) the source typed-array to this (worker proxy owned) web worker.
    let message = { command: "typedArray_processTensor", processingId: processingId, sourceTypedArray: sourceTypedArray };
    this.worker.postMessage( message );

    return processRelayPromises.process.promise;
  }

  /**
   * Handle messages from the progress of loading library of web workers.
   */
  initLibraryProgress_onReport( workerId ) {
//!!! ...unfinished...
  }

  /**
   * Handle messages from the progress of loading neural network of web workers.
   */
  initNeuralNetProgress_onReport( workerId ) {
//!!! ...unfinished...
  }

  /**
   * Called when the scaled source typed-array from web worker is received.
   */
  on_transferBackSourceTypedArray( workerId, processingId, sourceTypedArray ) {

    if ( workerId != this.workerId )
      return; // Ignore if wrong worker id.

    let processRelayPromises = this.processRelayPromisesMap.get( processingId );
    if ( !processRelayPromises )
      return; // Ignore if processing id does not existed. (e.g. already handled)

    processRelayPromises.relay.resolve( sourceTypedArray ); // This will be received by WorkerController.

    // Here, the processRelayPromises should not be removed from processRelayPromisesMap.
    // It will be removed when processTensor() done completely (i.e. inside on_processTensorResult()).
  }

  /**
   * Called when the processed tensor from web worker is received.
   *
   * @param {number} workerId
   *   The id of the worker which sent the result back.
   *
   * @param {number} processingId
   *   The processing id of the result.
   *
   * @param {TypedArray} resultTypedArray
   *   The result of the returned processing. It is the downloaded data of the result tensor.
   */
  on_processTensorResult( workerId, processingId, resultTypedArray ) {

    if ( workerId != this.workerId )
      return; // Ignore if wrong worker id.

    let processRelayPromises = this.processRelayPromisesMap.get( processingId );
    if ( !processRelayPromises )
      return; // Discard result with non-existed processing id. (e.g. already handled old processing result)

    processRelayPromises.process.resolve( resultTypedArray );

//!!! ...unfinished... When will fail?
    //processRelayPromises.reject();

//!!! ...unfinished... Whether should the older (i.e. smaller) processingId be cleared from map? (Could the processing be out of order?)

    this.processRelayPromisesMap.delete( processingId ); // Clear the info entry of handled processing result.
  }

  /**
   * Dispatch messages come from the owned web worker.
   *
   * @param {Base} this
   *   Should be binded to this object.
   */
  static onmessage_fromWorker( e ) {
    let message = e.data;

    switch ( message.command ) {
      case "initLibraryProgressReport": //{ command: "initLibraryProgressReport", workerId, ,  };
//!!! ...unfinished...
        //this.initLibraryProgress_onReport( message.workerId, ,  );
        break;

      case "initNeuralNetProgressReport": //{ command: "initNeuralNetProgressReport", workerId, ,  };
//!!! ...unfinished...
        //this.initNeuralNetProgress_onReport( message.workerId, ,  );
        break;

      case "transferBackSourceTypedArray": //{ command: "transferBackSourceTypedArray", workerId, processingId, sourceTypedArray };
        this.on_transferBackSourceTypedArray( message.workerId, message.processingId, message.sourceTypedArray );
        break;

      case "processTensorResult": //{ command: "processTensorResult", workerId, processingId, resultTypedArray };
        this.on_processTensorResult( message.workerId, message.processingId, message.resultTypedArray );
        break;

    }
  }

}
<|endoftext|>


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

/**
 * Creates a composite report of all of the 3rd party images, code, audio and other media used by all of the PhET
 * Simulations which appear in a directory (required argument) and updates the online version by automatically adding and
 * pushing the changes to GitHub.  One of the reports is published at:
 * https://github.com/phetsims/sherpa/blob/master/third-party-licenses.md
 *
 * Usage:
 * cd chipper
 * grunt report-third-party
 * // then push the updated license file to sherpa
 *
 * NOTE: sometimes for unknown reasons a file is missed or skipped, please inspect the third-party-licenses.md before
 * committing to make sure it is complete.
 *
 * Third party entries are parsed from HTML, see getLicenseEntry.js
 *
 * If the --active-runnables grunt option is supplied, the task iterates over the active-runnables and copies each built HTML file
 * into the directory specified with --input before running the report.  If any HTML files are missing, the report will fail.
 * Before using this options, the developer should run `grunt-all.sh` to make sure all of the HTML files are up-to-date.
 * The directory is neither automatically created nor automatically cleaned, this is the responsibility of the developer.
 * (Note that if you fail to manually clean the directory, you may end up with stale HTML files).
 *
 * See https://github.com/phetsims/chipper/issues/162
 *
 * @author Sam Reid
 */

// Load shared constants
var ChipperConstants = require( '../../../chipper/js/common/ChipperConstants' );
var ChipperStringUtils = require( '../../../chipper/js/common/ChipperStringUtils' );

// constants
var SHERPA = '../sherpa';  // The relative path to sherpa, from the chipper path

// node modules
var fs = require( 'fs' );
var http = require( 'http' );

var _ = require( '../../../sherpa/lib/lodash-2.4.1.min' ); // eslint-disable-line require-statement-match

/**
 * @param grunt - the grunt instance
 */
module.exports = function( grunt ) {
  'use strict';

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

  // Since we download the HTML files, this task must wait until it is complete
  var gruntDone = grunt.task.current.async();

  // Prepare a directory where the HTML files will be downloaded
  if ( grunt.file.exists( 'downloaded-sims' ) ) {
    grunt.file.delete( 'downloaded-sims' );
  }
  grunt.file.mkdir( 'downloaded-sims' );

  // Download a URL to a local file
  // http://stackoverflow.com/questions/11944932/how-to-download-a-file-with-node-js-without-using-third-party-libraries
  var download = function( url, dest, cb ) {
    var file = fs.createWriteStream( dest );
    http.get( url, function( response ) {
      response.pipe( file );
      file.on( 'finish', function() {
        file.close( cb );  // close() is async, call cb after close completes.
      } );
    } ).on( 'error', function( err ) { // Handle errors
      fs.unlink( dest ); // Delete the file async. (But we don't check the result)
      if ( cb ) {
        cb( err.message );
      }
    } );
  };

  // After the downloads are complete, write the report based on the metadata in the files.
  var writeReport = function() {

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

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

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

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

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

    /**
     * Add the source (images/audio/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
     */
    var augment = function( repositoryName, source, destination ) {
      for ( var entry in source ) {
        if ( source.hasOwnProperty( entry ) ) {
          if ( !destination.hasOwnProperty( entry ) ) {
            destination[ entry ] = source[ entry ];//overwrites
            destination[ entry ].usedBy = [];
          }
          destination[ entry ].usedBy.push( repositoryName );
        }
      }
    };

    grunt.file.recurse( 'downloaded-sims', function( abspath, rootdir, subdir, filename ) {
      if ( ChipperStringUtils.endsWith( filename.toLowerCase(), '.html' ) &&

           // shortcut to avoid looking at the -iframe.html files when testing on a build directory.
           !ChipperStringUtils.endsWith( filename.toLowerCase(), '-iframe.html' ) ) {

        // load the file into a string
        var html = grunt.file.read( abspath ).trim();

        if ( html.indexOf( 'was not found on this server' ) < 0 || html.indexOf( 'was not found on this server' ) > 400 ) {

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

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

          var json = JSON.parse( jsonString );

          var title = parseTitle( html );
          if ( !title || title.indexOf( 'undefined' ) === 0 || title.indexOf( 'TITLE' ) >= 0 ) {
            grunt.log.writeln( 'title not found for ' + abspath );
            title = filename;
          }
          augment( title, json.lib, compositeCode );
          augment( title, json.audio, compositeMedia );
          augment( title, json.images, compositeMedia );

          simTitles.push( title );
        }
      }
    } );

    // 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 ( var entry in compositeCode ) {
      if ( compositeCode.hasOwnProperty( entry ) ) {
        compositeCode[ entry ].usedBy.sort();
        if ( _.isEqual( simTitles, compositeCode[ entry ].usedBy ) ) {
          compositeCode[ entry ].usedBy = 'all-sims';// Confusing that this matches a repo name, but since that repo isn't actually a sim, perhaps this will be ok
        }
      }
    }

    var json = grunt.file.readJSON( SHERPA + '/lib/license.json' );

    var entries = [];
    var codeLicensesUsed = [];
    var mediaLicensesUsed = [];

    // Get a list of the library names
    var libraries = [];
    for ( var lib in json ) {
      if ( json.hasOwnProperty( lib ) ) {
        libraries.push( lib );
      }
    }

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

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

      var lines = [
        '**' + library + '**',
        json[ library ].text.join( '
' ),
        json[ library ].projectURL,
        'License: [' + json[ library ].license + '](licenses/' + library + '.txt)',
        'Notes: ' + json[ library ].notes
      ];

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

      if ( compositeCode.hasOwnProperty( library ) && compositeCode[ library ].usedBy instanceof Array ) {
        lines.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
      entries.push( lines.join( '
' ) );

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

    var mediaOutput = [];
    var mediaKeys = [];
    for ( var 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 audio, and keep track of which licenses were used by them.
    for ( i = 0; i < mediaKeys.length; i++ ) {
      var mediaKey = mediaKeys[ i ];

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

      if ( copyrightStatement.length === 0 ) {
        copyrightStatement = '(no text)';
      }
      if ( projectURL.length === 0 ) {
        projectURL = '(no project url)';
      }
      var notes = compositeMedia[ mediaKey ].notes.trim();
      if ( notes.length === 0 ) {
        notes = '(no notes)';
      }
      var license = compositeMedia[ mediaKey ].license.trim();
      if ( license.length === 0 ) {
        license = '(no license)';
      }
      var mediaEntryLines = [
        '**' + mediaKey + '**',
        copyrightStatement,
        projectURL,
        'License: ' + license,
        'Notes: ' + notes
      ];
      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
      // contact phethelp@colorado.edu are leaking into the HTML files unnecessarily
      if ( license !== 'contact phethelp@colorado.edu' ) {
        mediaOutput.push( mediaEntryLines.join( '
' ) );

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

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

    var outputString =
      'This report enumerates the third-party resources (code, images, audio, 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' +
      entries.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( output ) || grunt.file.read( output ) !== outputString ) {
      grunt.log.writeln( 'File output changed, writing file ' + output );
      grunt.file.write( output, outputString );
    }
    else {
      grunt.log.writeln( output + ' contents are the same.  No need to save.' );
    }

    gruntDone();
  };

  // Download all of the simulations.  For simulations that are in "active-sims" but not published,
  // download them anyways but skip them in the report.
  var activeSimsString = fs.readFileSync( '../chipper/data/active-sims', 'utf-8' ).trim();
  var activeSimsArray = activeSimsString.split( '\n' );

  // Download files one at a time so we can make sure we get everything.
  var downloadNext = function( index ) {
    var sim = activeSimsArray[ index ];

    var url = 'http://phet.colorado.edu/sims/html/' + sim + '/latest/' + sim + '_en.html';
    console.log( 'downloading ' + (index + 1) + '/' + activeSimsArray.length + ': ' + url );

    download( url, 'downloaded-sims/' + sim + '_en.html', function() {
      var next = index + 1;

      if ( next < activeSimsArray.length ) {
        downloadNext( next );
      }
      else {
        writeReport();
      }
    } );
  };

  downloadNext( 0 );

  activeSimsArray.forEach( function( sim ) {

  } );
};<commit_msg>Cleaned up docs, see https://github.com/phetsims/sherpa/issues/64<commit_after>// Copyright 2015, University of Colorado Boulder

/**
 * Creates a composite report of all of the 3rd party images, code, audio 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:
 * cd chipper
 * grunt report-third-party
 * // then push the updated license file to sherpa
 *
 * Third party entries are parsed from HTML, see getLicenseEntry.js
 *
 * @author Sam Reid
 */

// Load shared constants
var ChipperConstants = require( '../../../chipper/js/common/ChipperConstants' );
var ChipperStringUtils = require( '../../../chipper/js/common/ChipperStringUtils' );

// constants
var SHERPA = '../sherpa';  // The relative path to sherpa, from the chipper path

// node modules
var fs = require( 'fs' );
var http = require( 'http' );

var _ = require( '../../../sherpa/lib/lodash-2.4.1.min' ); // eslint-disable-line require-statement-match

/**
 * @param grunt - the grunt instance
 */
module.exports = function( grunt ) {
  'use strict';

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

  // Since we download the HTML files, this task must wait until it is complete
  var gruntDone = grunt.task.current.async();

  // Prepare a directory where the HTML files will be downloaded
  if ( grunt.file.exists( 'downloaded-sims' ) ) {
    grunt.file.delete( 'downloaded-sims' );
  }
  grunt.file.mkdir( 'downloaded-sims' );

  // Download a URL to a local file
  // http://stackoverflow.com/questions/11944932/how-to-download-a-file-with-node-js-without-using-third-party-libraries
  var download = function( url, dest, cb ) {
    var file = fs.createWriteStream( dest );
    http.get( url, function( response ) {
      response.pipe( file );
      file.on( 'finish', function() {
        file.close( cb );  // close() is async, call cb after close completes.
      } );
    } ).on( 'error', function( err ) { // Handle errors
      fs.unlink( dest ); // Delete the file async. (But we don't check the result)
      if ( cb ) {
        cb( err.message );
      }
    } );
  };

  // After the downloads are complete, write the report based on the metadata in the files.
  var writeReport = function() {

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

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

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

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

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

    /**
     * Add the source (images/audio/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
     */
    var augment = function( repositoryName, source, destination ) {
      for ( var entry in source ) {
        if ( source.hasOwnProperty( entry ) ) {
          if ( !destination.hasOwnProperty( entry ) ) {
            destination[ entry ] = source[ entry ];//overwrites
            destination[ entry ].usedBy = [];
          }
          destination[ entry ].usedBy.push( repositoryName );
        }
      }
    };

    grunt.file.recurse( 'downloaded-sims', function( abspath, rootdir, subdir, filename ) {
      if ( ChipperStringUtils.endsWith( filename.toLowerCase(), '.html' ) &&

           // shortcut to avoid looking at the -iframe.html files when testing on a build directory.
           !ChipperStringUtils.endsWith( filename.toLowerCase(), '-iframe.html' ) ) {

        // load the file into a string
        var html = grunt.file.read( abspath ).trim();

        if ( html.indexOf( 'was not found on this server' ) < 0 || html.indexOf( 'was not found on this server' ) > 400 ) {

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

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

          var json = JSON.parse( jsonString );

          var title = parseTitle( html );
          if ( !title || title.indexOf( 'undefined' ) === 0 || title.indexOf( 'TITLE' ) >= 0 ) {
            grunt.log.writeln( 'title not found for ' + abspath );
            title = filename;
          }
          augment( title, json.lib, compositeCode );
          augment( title, json.audio, compositeMedia );
          augment( title, json.images, compositeMedia );

          simTitles.push( title );
        }
      }
    } );

    // 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 ( var entry in compositeCode ) {
      if ( compositeCode.hasOwnProperty( entry ) ) {
        compositeCode[ entry ].usedBy.sort();
        if ( _.isEqual( simTitles, compositeCode[ entry ].usedBy ) ) {
          compositeCode[ entry ].usedBy = 'all-sims';// Confusing that this matches a repo name, but since that repo isn't actually a sim, perhaps this will be ok
        }
      }
    }

    var json = grunt.file.readJSON( SHERPA + '/lib/license.json' );

    var entries = [];
    var codeLicensesUsed = [];
    var mediaLicensesUsed = [];

    // Get a list of the library names
    var libraries = [];
    for ( var lib in json ) {
      if ( json.hasOwnProperty( lib ) ) {
        libraries.push( lib );
      }
    }

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

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

      var lines = [
        '**' + library + '**',
        json[ library ].text.join( '
' ),
        json[ library ].projectURL,
        'License: [' + json[ library ].license + '](licenses/' + library + '.txt)',
        'Notes: ' + json[ library ].notes
      ];

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

      if ( compositeCode.hasOwnProperty( library ) && compositeCode[ library ].usedBy instanceof Array ) {
        lines.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
      entries.push( lines.join( '
' ) );

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

    var mediaOutput = [];
    var mediaKeys = [];
    for ( var 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 audio, and keep track of which licenses were used by them.
    for ( i = 0; i < mediaKeys.length; i++ ) {
      var mediaKey = mediaKeys[ i ];

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

      if ( copyrightStatement.length === 0 ) {
        copyrightStatement = '(no text)';
      }
      if ( projectURL.length === 0 ) {
        projectURL = '(no project url)';
      }
      var notes = compositeMedia[ mediaKey ].notes.trim();
      if ( notes.length === 0 ) {
        notes = '(no notes)';
      }
      var license = compositeMedia[ mediaKey ].license.trim();
      if ( license.length === 0 ) {
        license = '(no license)';
      }
      var mediaEntryLines = [
        '**' + mediaKey + '**',
        copyrightStatement,
        projectURL,
        'License: ' + license,
        'Notes: ' + notes
      ];
      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
      // contact phethelp@colorado.edu are leaking into the HTML files unnecessarily
      if ( license !== 'contact phethelp@colorado.edu' ) {
        mediaOutput.push( mediaEntryLines.join( '
' ) );

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

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

    var outputString =
      'This report enumerates the third-party resources (code, images, audio, 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' +
      entries.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( output ) || grunt.file.read( output ) !== outputString ) {
      grunt.log.writeln( 'File output changed, writing file ' + output );
      grunt.file.write( output, outputString );
    }
    else {
      grunt.log.writeln( output + ' contents are the same.  No need to save.' );
    }

    gruntDone();
  };

  // Download all of the simulations.  For simulations that are in "active-sims" but not published,
  // download them anyways but skip them in the report.
  var activeSimsString = fs.readFileSync( '../chipper/data/active-sims', 'utf-8' ).trim();
  var activeSimsArray = activeSimsString.split( '\n' );

  // Download files one at a time so we can make sure we get everything.
  var downloadNext = function( index ) {
    var sim = activeSimsArray[ index ];

    var url = 'http://phet.colorado.edu/sims/html/' + sim + '/latest/' + sim + '_en.html';
    console.log( 'downloading ' + (index + 1) + '/' + activeSimsArray.length + ': ' + url );

    download( url, 'downloaded-sims/' + sim + '_en.html', function() {
      var next = index + 1;

      if ( next < activeSimsArray.length ) {
        downloadNext( next );
      }
      else {
        writeReport();
      }
    } );
  };

  downloadNext( 0 );

  activeSimsArray.forEach( function( sim ) {

  } );
};<|endoftext|>