<commit_before>import React, {Component} from 'react';
import Swipeable from '../../js/Swipeable';

const initialState = {
  swiping: false,
  swiped: false,
  swipingDirection: '',
  swipedDirection: '',
};
const initialStateSwipeable = {
  flickThreshold: '0.6',
  delta: '10',
};

export default class Main extends Component {
  constructor(props) {
    super(props);
    this.state = Object.assign({}, initialState, initialStateSwipeable);
  }

  resetState(resetAll) {
    if (resetAll) {
      this.setState(Object.assign({}, initialState, initialStateSwipeable));
    } else {
      this.setState(initialState);
    }
  }

  onSwiped(...args) {
    console.log('swiped args: ', args)
    this.setState({
      swiped: true,
      swiping: false,
    });
  }

  onSwiping(...args) {
    console.log('swiping args: ', args)
    this.setState({
      swiping: true,
      swiped: false,
    });
  }

  onSwipedDirection(direction) {
    this.setState({
      swipedDirection: direction,
    });
  }

  onSwipingDirection(direction) {
    this.setState({
      swipingDirection: direction,
    });
  }

  updateValue(type, value) {
    this.setState({
      [type]: value,
    });
  }

  render() {
    const {
      swiping,
      swiped,
      swipingDirection,
      swipedDirection,
      flickThreshold,
      delta,
    } = this.state;
    const boundSwipes = getBoundSwipes(this);
    const isFlickThresholdNumber = !(isNaN(flickThreshold) || flickThreshold === '');
    const isDeltaNumber = !(isNaN(delta) || delta === '');
    const flickThresholdNum = isFlickThresholdNumber ? +flickThreshold : 0.6;
    const deltaNum = isDeltaNumber ? +delta : 10;
    return (

react-swipeable

Swipe bindings for react

               View on GitHub

              <Swipeable {...boundSwipes}
                onSwiping={(...args)=>this.onSwiping(...args)}
                onSwiped={(...args)=>this.onSwiped(...args)}
                flickThreshold={flickThresholdNum}
                delta={deltaNum}>

this.resetState()}
                  style={{height: '150px'}}>

Swipe inside here...

              </Swipeable>

Action	Output

onSwiping	{swiping ? 'True' : 'False'}

onSwiped	{swiped ? 'True' : 'False'}

onSwiping[Direction]	{swipingDirection}

onSwiped[Direction]	{swipedDirection}

                  delta:

                    <input type="text"
                      style={{margin: '0px', border: !isDeltaNumber ? '2px solid red' : ''}}
                      onChange={(e)=>this.updateValue('delta', getVal(e))} value={delta}/>

                  flickThreshold:

                    <input type="text"
                      style={{margin: '0px', border: !isFlickThresholdNumber ? '2px solid red' : ''}}
                      onChange={(e)=>this.updateValue('flickThreshold', getVal(e))} value={flickThreshold}/>

              <button type="button" className="tiny button expanded" onClick={()=>this.resetState(true)}>Reset</button>

You can also check the console log for 'onSwiping' and 'onSwiped' callback output

    )
  }
}

function getBoundSwipes(component) {
  const directions = ['Left', 'Right', 'Up', 'Down'];
  let boundSwipes = {};
  directions.forEach((dir)=>{
    boundSwipes[`onSwiped${dir}`] = component.onSwipedDirection.bind(component, dir);
    boundSwipes[`onSwiping${dir}`] = component.onSwipingDirection.bind(component, dir);
  });
  return boundSwipes;
}

function getVal(e) {
  return e.target.value;
}
<commit_msg>add ability to toggle onSwiped/ing props being applied in example<commit_after>import React, {Component} from 'react';
import Swipeable from '../../js/Swipeable';

const DIRECTIONS = ['Left', 'Right', 'Up', 'Down'];

const initialState = {
  swiping: false,
  swiped: false,
  swipingDirection: '',
  swipedDirection: '',
};
const initialStateSwipeable = {
  flickThreshold: '0.6',
  delta: '10',
};
const initialStateApplied = {
  onSwipingApplied: true,
  onSwipedApplied: true,
  onSwipingLeftApplied: true,
  onSwipingRightApplied: true,
  onSwipingUpApplied: true,
  onSwipingDownApplied: true,
  onSwipedLeftApplied: true,
  onSwipedRightApplied: true,
  onSwipedUpApplied: true,
  onSwipedDownApplied: true,
};

export default class Main extends Component {
  constructor(props) {
    super(props);
    this.state = Object.assign({}, initialState, initialStateSwipeable, initialStateApplied);
  }

  resetState(resetAll) {
    if (resetAll) {
      this.setState(Object.assign({}, initialState, initialStateSwipeable, initialStateApplied));
    } else {
      this.setState(initialState);
    }
  }

  onSwiped(...args) {
    console.log('swiped args: ', args)
    this.setState({
      swiped: true,
      swiping: false,
    });
  }

  onSwiping(...args) {
    console.log('swiping args: ', args)
    this.setState({
      swiping: true,
      swiped: false,
    });
  }

  onSwipedDirection(direction) {
    this.setState({
      swipedDirection: direction,
    });
  }

  onSwipingDirection(direction) {
    this.setState({
      swipingDirection: direction,
    });
  }

  updateValue(type, value) {
    this.setState({
      [type]: value,
    });
  }

  _renderAppliedDirRow(dir) {
    return (
      <tr key={`appliedDirRow${dir}`}>

          <input type="checkbox" checked={this.state[`onSwiping${dir}Applied`]}
            onChange={(e)=>this.updateValue(`onSwiping${dir}Applied`, e.target.checked)} />

        <td style={{color: this.state[`onSwiping${dir}Applied`] ? '#000000' : '#cccccc', borderRight: "1px solid #cccccc"}}>{dir}

          <input type="checkbox" checked={this.state[`onSwiped${dir}Applied`]}
            onChange={(e)=>this.updateValue(`onSwiped${dir}Applied`, e.target.checked)} />

        <td style={{color: this.state[`onSwiped${dir}Applied`] ? '#000000' : '#cccccc'}}>{dir}

    )
  }

  render() {
    const {
      swiping,
      swiped,
      swipingDirection,
      swipedDirection,
      flickThreshold,
      delta,
      onSwipingApplied,
      onSwipedApplied,
    } = this.state;

    const isFlickThresholdNumber = !(isNaN(flickThreshold) || flickThreshold === '');
    const isDeltaNumber = !(isNaN(delta) || delta === '');
    const flickThresholdNum = isFlickThresholdNumber ? +flickThreshold : 0.6;
    const deltaNum = isDeltaNumber ? +delta : 10;

    const boundSwipes = getBoundSwipes(this);
    let swipeableDirProps = {};
    if (onSwipingApplied) {
      swipeableDirProps.onSwiping = (...args)=>this.onSwiping(...args);
    }
    if (onSwipedApplied) {
      swipeableDirProps.onSwiped = (...args)=>this.onSwiped(...args);
    }

    return (

react-swipeable View on GitHub

              <Swipeable {...boundSwipes}
                {...swipeableDirProps}
                flickThreshold={flickThresholdNum}
                delta={deltaNum}>

this.resetState()}
                  style={{height: '150px', fontSize: "0.75rem"}}>

Swipe inside here to test...

See output below and check the console for 'onSwiping' and 'onSwiped' callback output

                  You can also 'toggle' the swip(ed/ing) props being applied to this container below.

              </Swipeable>

Applied?	Action	Output

                    <input type="checkbox" checked={onSwipingApplied}
                      onChange={(e)=>this.updateValue('onSwipingApplied', e.target.checked)} />

                  onSwiping	{swiping ? 'True' : 'False'}

                    <input type="checkbox" checked={onSwipedApplied}
                      onChange={(e)=>this.updateValue('onSwipedApplied', e.target.checked)} />

                  onSwiped	{swiped ? 'True' : 'False'}

                  Below
                  onSwiping[Direction]	{swipingDirection}

                  Below
                  onSwiped[Direction]	{swipedDirection}

                  delta:

                    <input type="text"
                      style={{margin: '0px', border: !isDeltaNumber ? '2px solid red' : ''}}
                      onChange={(e)=>this.updateValue('delta', getVal(e))} value={delta}/>

                  flickThreshold:

                    <input type="text"
                      style={{margin: '0px', border: !isFlickThresholdNumber ? '2px solid red' : ''}}
                      onChange={(e)=>this.updateValue('flickThreshold', getVal(e))} value={flickThreshold}/>

              <button type="button" className="tiny button expanded" onClick={()=>this.resetState(true)}>Reset All Options</button>

onSwiping	onSwiped

Applied?	Direction	Applied?	Direction

                  {DIRECTIONS.map(this._renderAppliedDirRow.bind(this))}

    )
  }
}

function getBoundSwipes(component) {
  let boundSwipes = {};
  DIRECTIONS.forEach((dir)=>{
    if (component.state[`onSwiped${dir}Applied`]) {
      boundSwipes[`onSwiped${dir}`] = component.onSwipedDirection.bind(component, dir);
    }
    if (component.state[`onSwiping${dir}Applied`]) {
      boundSwipes[`onSwiping${dir}`] = component.onSwipingDirection.bind(component, dir);
    }
  });
  return boundSwipes;
}

function getVal(e) {
  return e.target.value;
}
<|endoftext|>


<commit_before><commit_msg>sample for motion<commit_after><|endoftext|>


<commit_before>'use strict';

// These are nominally based on improv sample.json delta stream with
// the addition of puppet subordinate relations.

describe('Relation endpoints logic', function() {
  var Y, juju, utils, db, app, models, sample_endpoints, sample_env, env;

  before(function(done) {
    Y = YUI(GlobalConfig).use([
      'io', 'json-parse', 'array-extras'], function(Y) {
      function loadFixture(url) {
        return Y.JSON.parse(Y.io(url, {sync: true}).responseText);
      }
      sample_env = loadFixture('data/large_stream.json');
      sample_endpoints = loadFixture('data/large_endpoints.json');
      done();
    });
  });

  beforeEach(function(done) {
    Y = YUI(GlobalConfig).use(['juju-models',
                               'juju-gui',
                               'juju-tests-utils',
                               'juju-controllers'],
    function(Y) {
      juju = Y.namespace('juju');
      utils = Y.namespace('juju-tests.utils');
      models = Y.namespace('juju.models');
      var conn = new utils.SocketStub();
      env = juju.newEnvironment({conn: conn});
      env.connect();
      app = new Y.juju.App({env: env});
      app.showView(new Y.View());
      db = app.db;
      db.onDelta({data: {'op': 'delta', result: sample_env}});
      done();
    });
  });

  afterEach(function(done) {
    app.destroy();
    db.destroy();
    env.destroy();
    done();
  });

  it('should be able to find relatable services', function() {
    app.endpointsController.endpointsMap = sample_endpoints;
    var service = db.services.getById('blog-lb'),
        available_svcs = Y.Object.keys(models.getEndpoints(
            service, app.endpointsController));
    available_svcs.sort();
    available_svcs.should.eql(
        ['mediawiki', 'puppet', 'rsyslog-forwarder', 'wiki-lb', 'wordpress']);
  });

  it('should find valid targets including subordinates', function() {
    app.endpointsController.endpointsMap = sample_endpoints;
    var service = db.services.getById('memcached'),
        available = models.getEndpoints(service, app.endpointsController),
        available_svcs;
    available_svcs = Y.Object.keys(available);
    available_svcs.sort();
    available_svcs.should.eql(
        ['puppet', 'rsyslog-forwarder', 'wordpress']);
  });

  it('should find ambigious targets', function() {
    // Mysql already has both subordinates related.
    app.endpointsController.endpointsMap = sample_endpoints;
    var service = db.services.getById('mysql'),
        available = models.getEndpoints(service, app.endpointsController),
        available_svcs = Y.Object.keys(available);
    available_svcs.sort();
    available_svcs.should.eql(['mediawiki']);
    // mediawiki has two possible relations (read slave or write master)
    // it can establish with mysql.
    available.mediawiki.should.eql([
      [{name: 'db', service: 'mysql', type: 'mysql'},
       {name: 'slave', service: 'mediawiki', type: 'mysql'}],
      [{name: 'db', service: 'mysql', type: 'mysql'},
       {name: 'db', service: 'mediawiki', type: 'mysql'}]
    ]);

    // Demonstrate the inverse retrieval of the same.
    available = models.getEndpoints(
        db.services.getById('mediawiki'), app.endpointsController);
    available.mysql.should.eql([
      [{name: 'slave', service: 'mediawiki', type: 'mysql'},
       {name: 'db', service: 'mysql', type: 'mysql'}],
      [{name: 'db', service: 'mediawiki', type: 'mysql'},
       {name: 'db', service: 'mysql', type: 'mysql'}]
    ]);
  });

  it('should find valid targets for a requires', function() {
    // Wordpress already has one subordinates related (puppet)
    // ..the picture is wrong.. wordpress only has one subordinate
    app.endpointsController.endpointsMap = sample_endpoints;
    var service = db.services.getById('wordpress'),
        available = models.getEndpoints(service, app.endpointsController),
        available_svcs = Y.Object.keys(available);
    available_svcs.sort();
    available_svcs.should.eql(
        ['blog-lb', 'memcached', 'rsyslog-forwarder']);
  });

  it('should find valid targets for subordinates', function() {
    app.endpointsController.endpointsMap = sample_endpoints;
    var service = db.services.getById('puppet');
    var available = models.getEndpoints(service, app.endpointsController);
    var available_svcs = Y.Object.keys(available);

    available_svcs.sort();
    available_svcs.should.eql(
        ['blog-lb', 'memcached', 'puppetmaster', 'rsyslog', 'wiki-lb']);

    service = db.services.getById('rsyslog-forwarder');
    available = models.getEndpoints(service, app.endpointsController);
    available_svcs = Y.Object.keys(available);

    available_svcs.sort();
    available_svcs.should.eql(
        ['blog-lb', 'memcached',
         'puppetmaster', 'rsyslog', 'wiki-lb', 'wordpress']);
  });

});

describe('Endpoints map', function() {
  var Y, juju, models, controller;

  beforeEach(function(done) {
    Y = YUI(GlobalConfig).use(['juju-models',
                               'juju-tests-utils',
                               'juju-endpoints-controller',
                               'juju-controllers'],
    function(Y) {
      juju = Y.namespace('juju');
      models = Y.namespace('juju.models');
      var EndpointsController = Y.namespace('juju.EndpointsController');
      controller = new EndpointsController();
      done();
    });
  });

  afterEach(function(done) {
    controller.destroy();
    done();
  });

  it('should add a service to the map', function() {
    controller.endpointsMap = {};
    var charm = new models.Charm({id: 'cs:precise/wordpress-2'});
    charm.set('provides', {
      url: {
        'interface': 'http',
        optional: 'false'
      },
      'logging-dir': {
        'interface': 'logging',
        scope: 'container'
      }
    });
    charm.set('requires', {
      db: {
        'interface': 'mysql',
        optional: 'false'
      },
      cache: {
        'interface': 'varnish',
        optional: 'true'
      }
    });
    controller.addServiceToEndpointsMap('wordpress', charm);
    controller.endpointsMap.should.eql({wordpress: {
      provides: [
        {
          name: 'url',
          'interface': 'http',
          optional: 'false'
        }, {
          name: 'logging-dir',
          'interface': 'logging',
          scope: 'container'
        }
      ],
      requires: [
        {
          name: 'db',
          'interface': 'mysql',
          optional: 'false'
        }, {
          name: 'cache',
          'interface': 'varnish',
          optional: 'true'
        }
      ]
    }});
    charm.destroy();
  });

  it('should add a service to the map, requires only', function() {
    controller.endpointsMap = {};
    var charm = new models.Charm({id: 'cs:precise/wordpress-2'});
    charm.set('requires', {
      db: {
        'interface': 'mysql',
        optional: 'false'
      },
      cache: {
        'interface': 'varnish',
        optional: 'true'
      }
    });
    controller.addServiceToEndpointsMap('wordpress', charm);
    controller.endpointsMap.should.eql({wordpress: {
      provides: [],
      requires: [
        {
          name: 'db',
          'interface': 'mysql',
          optional: 'false'
        }, {
          name: 'cache',
          'interface': 'varnish',
          optional: 'true'
        }
      ]
    }});
    charm.destroy();
  });

  it('should add a service to the map, provides only', function() {
    controller.endpointsMap = {};
    var charm = new models.Charm({id: 'cs:precise/wordpress-2'});
    charm.set('provides', {
      url: {
        'interface': 'http',
        optional: 'false'
      },
      'logging-dir': {
        'interface': 'logging',
        scope: 'container'
      }
    });
    controller.addServiceToEndpointsMap('wordpress', charm);
    controller.endpointsMap.should.eql({wordpress: {
      requires: [],
      provides: [
        {
          name: 'url',
          'interface': 'http',
          optional: 'false'
        }, {
          name: 'logging-dir',
          'interface': 'logging',
          scope: 'container'
        }
      ] }});
    charm.destroy();
  });

  it('should add a service to the map, neither provides nor requires',
     function() {
       controller.endpointsMap = {};
       var charm = new models.Charm({id: 'cs:precise/wordpress-2'});
       controller.addServiceToEndpointsMap('wordpress', charm);
       controller.endpointsMap.should.eql({wordpress: {
         requires: [],
         provides: []}});
       charm.destroy();
     });

  it('should reset the map', function() {
    var testmap = {'columbus': 'sailed the ocean blue'};
    controller.endpointsMap = testmap;
    controller.endpointsMap.should.eql(testmap);
    controller.reset();
    controller.endpointsMap.should.eql({});
  });

});

describe('Endpoints map handlers', function() {
  var Y, juju, utils, models, app, conn, env, controller;

  before(function(done) {
    Y = YUI(GlobalConfig).use(['juju-gui',
                               'juju-models',
                               'juju-tests-utils',
                               'juju-endpoints-controller',
                               'juju-controllers',
                               'juju-charm-store',
                               'datasource-local'],
    function(Y) {
      juju = Y.namespace('juju');
      utils = Y.namespace('juju-tests.utils');
      models = Y.namespace('juju.models');
      done();
    });
  });

  beforeEach(function() {
    conn = new utils.SocketStub();
    env = juju.newEnvironment({conn: conn});
    env.connect();
    app = new Y.juju.App({env: env});
    app.showView(new Y.View());
    controller = app.endpointsController;
    controller.endpointsMap = {};
  });

  afterEach(function() {
    controller.destroy();
    env.destroy();
    app.destroy();
  });

  it('should not update endpoints map when pending services are added',
     function() {
       var charm_id = 'cs:precise/wordpress-2';
       app.db.services.add({
         id: 'wordpress',
         pending: true,
         charm: charm_id});
       controller.endpointsMap.should.eql({});
       // No charm should have tried to load (see bug 1166222).
       assert.isNull(app.db.charms.getById(charm_id));
     });

  it('should update endpoints map when non-pending services are added',
     function() {
       var service_name = 'wordpress';
       var charm_id = 'cs:precise/wordpress-2';
       app.db.charms.add({id: charm_id});
       var charm = app.db.charms.getById(charm_id);
       charm.loaded = true;
       app.db.services.add({
         id: service_name,
         pending: true,
         charm: charm_id});
       var svc = app.db.services.getById(service_name);
       svc.set('pending', false);
       controller.endpointsMap.should.eql({wordpress: {
         requires: [],
         provides: []}});
       charm.destroy();
     });

  it('should update endpoints map when a service\'s charm changes', function() {
    var service_name = 'wordpress';
    var charm_id = 'cs:precise/wordpress-2';
    app.db.charms.add({id: charm_id});
    var charm = app.db.charms.getById(charm_id);
    charm.loaded = true;
    app.db.services.add({
      id: service_name,
      pending: true,
      charm: charm_id});
    var svc = app.db.services.getById(service_name);
    svc.set('pending', false);
    controller.endpointsMap.should.eql({wordpress: {
      requires: [],
      provides: []}});

    charm_id = 'cs:precise/wordpress-3';
    app.db.charms.add({id: charm_id});
    var charm2 = app.db.charms.getById(charm_id);
    charm2.set('provides', {
      url: {
        'interface': 'http'
      }
    });

    charm2.loaded = true;
    svc.set('charm', charm_id);
    controller.endpointsMap.should.eql({wordpress: {
      requires: [],
      provides: [
        {
          name: 'url',
          'interface': 'http'
        }]}});
    charm.destroy();
    charm2.destroy();
  });

  it('should remove service from endpoints map when it is deleted', function() {
    var service_name = 'wordpress';
    var charm_id = 'cs:precise/wordpress-2';
    app.db.charms.add({id: charm_id});
    app.db.services.add({
      id: service_name,
      charm: charm_id});
    controller.endpointsMap = {wordpress: 'foo'};
    var service = app.db.services.getById(service_name);
    app.db.services.remove(service);
    controller.endpointsMap.should.eql({});
    app.db.charms.getById(charm_id).destroy();
  });

  it('should reset the map when the services reset', function() {
    var service_name = 'wordpress';
    var charm_id = 'cs:precise/wordpress-2';
    app.db.charms.add({id: charm_id});
    app.db.services.add({
      id: service_name,
      charm: charm_id});
    controller.endpointsMap = {wordpress: 'foo'};
    app.db.services.reset();
    controller.endpointsMap.should.eql({});
    app.db.charms.getById(charm_id).destroy();
  });

  it('should add the service to the endpoints map when the charm is loaded',
     function(done) {
       var service_name = 'wordpress';
       var charm_id = 'cs:precise/wordpress-2';
       app.db.charms.add({id: charm_id});
       controller.endpointsMap.should.eql({});
       var charm = app.db.charms.getById(charm_id);
       var data = [
         { responseText: Y.JSON.stringify(
         { summary: 'wowza', subordinate: true, store_revision: 7 })}];
       var charmStore = new juju.CharmStore({
         datasource: new Y.DataSource.Local({source: data})});

       app.db.services.add({
         id: service_name,
         pending: false,
         charm: charm_id});

       charm.load(charmStore, function(err, data) {
         if (err) { assert.fail('should succeed!'); }
         assert(charm.loaded);
         charm.get('summary').should.equal('wowza');

         controller.endpointsMap.should.eql({
           'wordpress': {
             provides: [],
             requires: []
           }});

         charmStore.destroy();
         done();
       });

     }
  );

});
<commit_msg>respond to review<commit_after>'use strict';

// These are nominally based on improv sample.json delta stream with
// the addition of puppet subordinate relations.

describe('Relation endpoints logic', function() {
  var Y, juju, utils, db, app, models, sample_endpoints, sample_env, env;

  before(function(done) {
    Y = YUI(GlobalConfig).use([
      'io', 'json-parse', 'array-extras'], function(Y) {
      function loadFixture(url) {
        return Y.JSON.parse(Y.io(url, {sync: true}).responseText);
      }
      sample_env = loadFixture('data/large_stream.json');
      sample_endpoints = loadFixture('data/large_endpoints.json');
      done();
    });
  });

  beforeEach(function(done) {
    Y = YUI(GlobalConfig).use(['juju-models',
                               'juju-gui',
                               'juju-tests-utils',
                               'juju-controllers'],
    function(Y) {
      juju = Y.namespace('juju');
      utils = Y.namespace('juju-tests.utils');
      models = Y.namespace('juju.models');
      var conn = new utils.SocketStub();
      env = juju.newEnvironment({conn: conn});
      env.connect();
      app = new Y.juju.App({env: env});
      app.showView(new Y.View());
      db = app.db;
      db.onDelta({data: {'op': 'delta', result: sample_env}});
      done();
    });
  });

  afterEach(function(done) {
    app.destroy();
    db.destroy();
    env.destroy();
    done();
  });

  it('should be able to find relatable services', function() {
    app.endpointsController.endpointsMap = sample_endpoints;
    var service = db.services.getById('blog-lb'),
        available_svcs = Y.Object.keys(models.getEndpoints(
            service, app.endpointsController));
    available_svcs.sort();
    available_svcs.should.eql(
        ['mediawiki', 'puppet', 'rsyslog-forwarder', 'wiki-lb', 'wordpress']);
  });

  it('should find valid targets including subordinates', function() {
    app.endpointsController.endpointsMap = sample_endpoints;
    var service = db.services.getById('memcached'),
        available = models.getEndpoints(service, app.endpointsController),
        available_svcs;
    available_svcs = Y.Object.keys(available);
    available_svcs.sort();
    available_svcs.should.eql(
        ['puppet', 'rsyslog-forwarder', 'wordpress']);
  });

  it('should find ambigious targets', function() {
    // Mysql already has both subordinates related.
    app.endpointsController.endpointsMap = sample_endpoints;
    var service = db.services.getById('mysql'),
        available = models.getEndpoints(service, app.endpointsController),
        available_svcs = Y.Object.keys(available);
    available_svcs.sort();
    available_svcs.should.eql(['mediawiki']);
    // mediawiki has two possible relations (read slave or write master)
    // it can establish with mysql.
    available.mediawiki.should.eql([
      [{name: 'db', service: 'mysql', type: 'mysql'},
       {name: 'slave', service: 'mediawiki', type: 'mysql'}],
      [{name: 'db', service: 'mysql', type: 'mysql'},
       {name: 'db', service: 'mediawiki', type: 'mysql'}]
    ]);

    // Demonstrate the inverse retrieval of the same.
    available = models.getEndpoints(
        db.services.getById('mediawiki'), app.endpointsController);
    available.mysql.should.eql([
      [{name: 'slave', service: 'mediawiki', type: 'mysql'},
       {name: 'db', service: 'mysql', type: 'mysql'}],
      [{name: 'db', service: 'mediawiki', type: 'mysql'},
       {name: 'db', service: 'mysql', type: 'mysql'}]
    ]);
  });

  it('should find valid targets for a requires', function() {
    // Wordpress already has one subordinates related (puppet)
    // ..the picture is wrong.. wordpress only has one subordinate
    app.endpointsController.endpointsMap = sample_endpoints;
    var service = db.services.getById('wordpress'),
        available = models.getEndpoints(service, app.endpointsController),
        available_svcs = Y.Object.keys(available);
    available_svcs.sort();
    available_svcs.should.eql(
        ['blog-lb', 'memcached', 'rsyslog-forwarder']);
  });

  it('should find valid targets for subordinates', function() {
    app.endpointsController.endpointsMap = sample_endpoints;
    var service = db.services.getById('puppet');
    var available = models.getEndpoints(service, app.endpointsController);
    var available_svcs = Y.Object.keys(available);

    available_svcs.sort();
    available_svcs.should.eql(
        ['blog-lb', 'memcached', 'puppetmaster', 'rsyslog', 'wiki-lb']);

    service = db.services.getById('rsyslog-forwarder');
    available = models.getEndpoints(service, app.endpointsController);
    available_svcs = Y.Object.keys(available);

    available_svcs.sort();
    available_svcs.should.eql(
        ['blog-lb', 'memcached',
         'puppetmaster', 'rsyslog', 'wiki-lb', 'wordpress']);
  });

});

describe('Endpoints map', function() {
  var Y, juju, models, controller;

  beforeEach(function(done) {
    Y = YUI(GlobalConfig).use(['juju-models',
                               'juju-tests-utils',
                               'juju-endpoints-controller',
                               'juju-controllers'],
    function(Y) {
      juju = Y.namespace('juju');
      models = Y.namespace('juju.models');
      var EndpointsController = Y.namespace('juju.EndpointsController');
      controller = new EndpointsController();
      done();
    });
  });

  afterEach(function(done) {
    controller.destroy();
    done();
  });

  it('should add a service to the map', function() {
    controller.endpointsMap = {};
    var charm = new models.Charm({id: 'cs:precise/wordpress-2'});
    charm.set('provides', {
      url: {
        'interface': 'http',
        optional: 'false'
      },
      'logging-dir': {
        'interface': 'logging',
        scope: 'container'
      }
    });
    charm.set('requires', {
      db: {
        'interface': 'mysql',
        optional: 'false'
      },
      cache: {
        'interface': 'varnish',
        optional: 'true'
      }
    });
    controller.addServiceToEndpointsMap('wordpress', charm);
    controller.endpointsMap.should.eql({wordpress: {
      provides: [
        {
          name: 'url',
          'interface': 'http',
          optional: 'false'
        }, {
          name: 'logging-dir',
          'interface': 'logging',
          scope: 'container'
        }
      ],
      requires: [
        {
          name: 'db',
          'interface': 'mysql',
          optional: 'false'
        }, {
          name: 'cache',
          'interface': 'varnish',
          optional: 'true'
        }
      ]
    }});
    charm.destroy();
  });

  it('should add a service to the map, requires only', function() {
    controller.endpointsMap = {};
    var charm = new models.Charm({id: 'cs:precise/wordpress-2'});
    charm.set('requires', {
      db: {
        'interface': 'mysql',
        optional: 'false'
      },
      cache: {
        'interface': 'varnish',
        optional: 'true'
      }
    });
    controller.addServiceToEndpointsMap('wordpress', charm);
    controller.endpointsMap.should.eql({wordpress: {
      provides: [],
      requires: [
        {
          name: 'db',
          'interface': 'mysql',
          optional: 'false'
        }, {
          name: 'cache',
          'interface': 'varnish',
          optional: 'true'
        }
      ]
    }});
    charm.destroy();
  });

  it('should add a service to the map, provides only', function() {
    controller.endpointsMap = {};
    var charm = new models.Charm({id: 'cs:precise/wordpress-2'});
    charm.set('provides', {
      url: {
        'interface': 'http',
        optional: 'false'
      },
      'logging-dir': {
        'interface': 'logging',
        scope: 'container'
      }
    });
    controller.addServiceToEndpointsMap('wordpress', charm);
    controller.endpointsMap.should.eql({wordpress: {
      requires: [],
      provides: [
        {
          name: 'url',
          'interface': 'http',
          optional: 'false'
        }, {
          name: 'logging-dir',
          'interface': 'logging',
          scope: 'container'
        }
      ] }});
    charm.destroy();
  });

  it('should add a service to the map, neither provides nor requires',
     function() {
       controller.endpointsMap = {};
       var charm = new models.Charm({id: 'cs:precise/wordpress-2'});
       controller.addServiceToEndpointsMap('wordpress', charm);
       controller.endpointsMap.should.eql({wordpress: {
         requires: [],
         provides: []}});
       charm.destroy();
     });

  it('should reset the map', function() {
    var testmap = {'columbus': 'sailed the ocean blue'};
    controller.endpointsMap = testmap;
    controller.endpointsMap.should.eql(testmap);
    controller.reset();
    controller.endpointsMap.should.eql({});
  });

});

describe('Endpoints map handlers', function() {
  var Y, juju, utils, models, app, conn, env, controller;

  before(function(done) {
    Y = YUI(GlobalConfig).use(['juju-gui',
                               'juju-models',
                               'juju-tests-utils',
                               'juju-endpoints-controller',
                               'juju-controllers',
                               'juju-charm-store',
                               'datasource-local'],
    function(Y) {
      juju = Y.namespace('juju');
      utils = Y.namespace('juju-tests.utils');
      models = Y.namespace('juju.models');
      done();
    });
  });

  beforeEach(function() {
    conn = new utils.SocketStub();
    env = juju.newEnvironment({conn: conn});
    env.connect();
    app = new Y.juju.App({env: env});
    app.showView(new Y.View());
    controller = app.endpointsController;
    controller.endpointsMap = {};
  });

  afterEach(function() {
    controller.destroy();
    env.destroy();
    app.destroy();
  });

  it('should not update endpoints map when pending services are added',
     function() {
       var charm_id = 'cs:precise/wordpress-2';
       app.db.services.add({
         id: 'wordpress',
         pending: true,
         charm: charm_id});
       assert.deepEqual(controller.endpointsMap, {});
       // No charm should have tried to load (see bug 1166222).
       assert.isNull(app.db.charms.getById(charm_id));
     });

  it('should update endpoints map when non-pending services are added',
     function() {
       var service_name = 'wordpress';
       var charm_id = 'cs:precise/wordpress-2';
       app.db.charms.add({id: charm_id});
       var charm = app.db.charms.getById(charm_id);
       charm.loaded = true;
       app.db.services.add({
         id: service_name,
         pending: true,
         charm: charm_id});
       var svc = app.db.services.getById(service_name);
       svc.set('pending', false);
       controller.endpointsMap.should.eql({wordpress: {
         requires: [],
         provides: []}});
       charm.destroy();
     });

  it('should update endpoints map when a service\'s charm changes', function() {
    var service_name = 'wordpress';
    var charm_id = 'cs:precise/wordpress-2';
    app.db.charms.add({id: charm_id});
    var charm = app.db.charms.getById(charm_id);
    charm.loaded = true;
    app.db.services.add({
      id: service_name,
      pending: true,
      charm: charm_id});
    var svc = app.db.services.getById(service_name);
    svc.set('pending', false);
    controller.endpointsMap.should.eql({wordpress: {
      requires: [],
      provides: []}});

    charm_id = 'cs:precise/wordpress-3';
    app.db.charms.add({id: charm_id});
    var charm2 = app.db.charms.getById(charm_id);
    charm2.set('provides', {
      url: {
        'interface': 'http'
      }
    });

    charm2.loaded = true;
    svc.set('charm', charm_id);
    controller.endpointsMap.should.eql({wordpress: {
      requires: [],
      provides: [
        {
          name: 'url',
          'interface': 'http'
        }]}});
    charm.destroy();
    charm2.destroy();
  });

  it('should remove service from endpoints map when it is deleted', function() {
    var service_name = 'wordpress';
    var charm_id = 'cs:precise/wordpress-2';
    app.db.charms.add({id: charm_id});
    app.db.services.add({
      id: service_name,
      charm: charm_id});
    controller.endpointsMap = {wordpress: 'foo'};
    var service = app.db.services.getById(service_name);
    app.db.services.remove(service);
    controller.endpointsMap.should.eql({});
    app.db.charms.getById(charm_id).destroy();
  });

  it('should reset the map when the services reset', function() {
    var service_name = 'wordpress';
    var charm_id = 'cs:precise/wordpress-2';
    app.db.charms.add({id: charm_id});
    app.db.services.add({
      id: service_name,
      charm: charm_id});
    controller.endpointsMap = {wordpress: 'foo'};
    app.db.services.reset();
    controller.endpointsMap.should.eql({});
    app.db.charms.getById(charm_id).destroy();
  });

  it('should add the service to the endpoints map when the charm is loaded',
     function(done) {
       var service_name = 'wordpress';
       var charm_id = 'cs:precise/wordpress-2';
       app.db.charms.add({id: charm_id});
       controller.endpointsMap.should.eql({});
       var charm = app.db.charms.getById(charm_id);
       var data = [
         { responseText: Y.JSON.stringify(
         { summary: 'wowza', subordinate: true, store_revision: 7 })}];
       var charmStore = new juju.CharmStore({
         datasource: new Y.DataSource.Local({source: data})});

       app.db.services.add({
         id: service_name,
         pending: false,
         charm: charm_id});

       charm.load(charmStore, function(err, data) {
         if (err) { assert.fail('should succeed!'); }
         assert(charm.loaded);
         charm.get('summary').should.equal('wowza');

         controller.endpointsMap.should.eql({
           'wordpress': {
             provides: [],
             requires: []
           }});

         charmStore.destroy();
         done();
       });

     }
  );

});
<|endoftext|>


<commit_before>const defaultTheme = require('tailwindcss/defaultTheme');

module.exports = {
    purge: ['./resources/**/*.blade.php', './resources/**/*.js'],
    theme: {
        extend: {
            colors: {
                lio: {
                    100: '#d1f2eb',
                    200: '#a3e4d7',
                    300: '#74d7c4',
                    400: '#46c9b0',
                    500: '#18bc9c',
                    600: '#15a589',
                    700: '#0e715e',
                    800: '#0a4b3e',
                    900: '#05261f',
                },
            },
            fontFamily: {
                sans: ['Inter var', ...defaultTheme.fontFamily.sans],
            },
        },
    },
    variants: {},
    plugins: [require('@tailwindcss/forms'), require('@tailwindcss/typography')],
};
<commit_msg>Update tailwind config<commit_after>const defaultTheme = require('tailwindcss/defaultTheme');

module.exports = {
    purge: [
        './resources/**/*.blade.php',
        './resources/**/*.js',
        './vendor/laravel/framework/src/Illuminate/Pagination/resources/views/*.blade.php',
    ],
    theme: {
        extend: {
            colors: {
                lio: {
                    100: '#d1f2eb',
                    200: '#a3e4d7',
                    300: '#74d7c4',
                    400: '#46c9b0',
                    500: '#18bc9c',
                    600: '#15a589',
                    700: '#0e715e',
                    800: '#0a4b3e',
                    900: '#05261f',
                },
            },
            fontFamily: {
                sans: ['Inter var', ...defaultTheme.fontFamily.sans],
            },
        },
    },
    variants: {},
    plugins: [require('@tailwindcss/forms'), require('@tailwindcss/typography')],
};
<|endoftext|>


<commit_before>'use strict';

var Hyphenizor = require('../lib/hyphenize')
var u = require('../lib/utils')

var assert = require('assert')

suite('Hyphenizor', function() {

	setup(function() {
	})

	test('Hyphenizor.languages', function () {
		Hyphenizor._lang = null
		let r = Hyphenizor.languages()
		assert(u.hash_size(r) >= 2)
		assert(u.hash_size(Hyphenizor._lang) >= 2)
	})

	test('tags_to_ignore_update', function () {
		let conf = {}
		let h = new Hyphenizor('', conf)
		let tags_len = u.hash_size(h.TAGS_TO_IGNORE)
		conf.tags_to_ignore = [1,2]
		h.tags_to_ignore_update()

		assert.equal(tags_len + 2, u.hash_size(h.TAGS_TO_IGNORE))
	})

	test('is_node_eligible', function () {
		let conf = {}
		let h = new Hyphenizor('', conf)

		assert.equal(false, h.is_node_eligible('hr'))
		assert.equal(true, h.is_node_eligible('p'))
	})

	test('lang_chose', function () {
		let conf = {lang: 'en'}
		let h = new Hyphenizor('', conf)
		assert('en', h.lang_chose(null))

		conf = {lang_detect: true, lang: 'uk'}
		h = new Hyphenizor('', conf)
		assert('uk', h.lang_chose(null))

		conf = {lang_detect: true, lang: 'es'}
		h = new Hyphenizor('', conf)
		assert('uk', h.lang_chose('uk'))
	})

	test('lang', function () {
		let conf = {lang: 'qwerty'}
		let h = new Hyphenizor('', conf)
		assert.throws(function() {
			h.lang(null)
		}, /no support for/)

		conf = {lang: 'en'}
		h = new Hyphenizor('', conf)
		assert(h.lang(null))
	})

	test('hyphenize', function () {
		let node = { '_': '2 characters' }
		let conf = {lang: 'en'}
		let h = new Hyphenizor('', conf)
		h.hyphenize(node, '_', null)
		assert.equal('2 characters', node['_'])

		node = { '_': '2 characters' }
		conf = {lang: 'uk'}
		h = new Hyphenizor('', conf)
		h.hyphenize(node, '_', null)
		assert.equal('2 characters', node['_'])
	})

	test('parse invalid xml', function(done) {
		let conf = {}
		let h = new Hyphenizor('<', conf)
		h.parse().catch(function(e) {
			assert(e.message.match(/Unclosed root tag/))
			done()
		});
	})

	test('parse empty input', function(done) {
		let conf = {}
		let h = new Hyphenizor('', conf)
		h.parse().then(function(r) {
			assert.equal('', h.toString())
			done()
		}).catch(function(e) {
			console.log(e)
		})
	})

	test('parse too simple xml', function(done) {
		let conf = {}
		let h = new Hyphenizor('<foo>characters</foo>', conf)
		h.parse().catch(function(e) {
			assert(e.message.match(/too simple xml/))
			done()
		})
	})

	test('parse 01 no lang', function(done) {
		let conf = {}
		let h = new Hyphenizor('<foo>characters</foo>', conf)
		h.parse().catch(function(e) {
			assert(e.message.match(/no support for .+ lang/))
			done()
		})
	})

	test('parse 01', function(done) {
		let conf = {lang: 'en'}
		let h = new Hyphenizor('<foo>characters</foo>', conf)
		h.parse().then(function(r) {
//			console.log(h.toString())
			assert.equal(`<?xml version="1.0" encoding="UTF-8"?>

  <foo>characters</foo>
`, h.toString())
			done()
		}).catch(function(e) {
			console.log(e)
		})
	})

	test('parse 02', function(done) {
		let conf = {lang_detect: true}
		let h = new Hyphenizor('<foo lang="en">characters</foo>Данко', conf)
		h.parse().then(function(r) {
//			console.log(h.toString())
			assert.equal(`<?xml version="1.0" encoding="UTF-8"?>

  Данко
  <foo lang="en">characters</foo>
`, h.toString())
			done()
		}).catch(function(e) {
			console.log(e)
		})
	})

})
<commit_msg>fix tests<commit_after>'use strict';

var Hyphenizor = require('../lib/hyphenize')
var u = require('../lib/utils')

var assert = require('assert')

suite('Hyphenizor', function() {

	setup(function() {
	})

	test('Hyphenizor.languages', function () {
		Hyphenizor._lang = null
		let r = Hyphenizor.languages()
		assert(u.hash_size(r) >= 2)
		assert(u.hash_size(Hyphenizor._lang) >= 2)
	})

	test('tags_to_ignore_update', function () {
		let conf = {}
		let h = new Hyphenizor('', conf)
		let tags_len = u.hash_size(h.TAGS_TO_IGNORE)
		conf.tags_to_ignore = [1,2]
		h.tags_to_ignore_update()

		assert.equal(tags_len + 2, u.hash_size(h.TAGS_TO_IGNORE))
	})

	test('is_node_eligible', function () {
		let conf = {}
		let h = new Hyphenizor('', conf)

		assert.equal(false, h.is_node_eligible('hr'))
		assert.equal(true, h.is_node_eligible('p'))
	})

	test('lang_chose', function () {
		let conf = {lang: 'en'}
		let h = new Hyphenizor('', conf)
		assert('en', h.lang_chose(null))

		conf = {lang_detect: true, lang: 'uk'}
		h = new Hyphenizor('', conf)
		assert('uk', h.lang_chose(null))

		conf = {lang_detect: true, lang: 'es'}
		h = new Hyphenizor('', conf)
		assert('uk', h.lang_chose('uk'))
	})

	test('lang', function () {
		let conf = {lang: 'qwerty'}
		let h = new Hyphenizor('', conf)
		assert.throws(function() {
			h.lang(null)
		}, /no support for/)

		conf = {lang: 'en'}
		h = new Hyphenizor('', conf)
		assert(h.lang(null))
	})

	test('hyphenize', function () {
		let node = { '_': '2 characters' }
		let conf = {lang: 'en'}
		let h = new Hyphenizor('', conf)
		h.hyphenize(node, '_', null)
		assert.equal('2 characters', node['_'])

		node = { '_': '2 characters' }
		conf = {lang: 'uk'}
		h = new Hyphenizor('', conf)
		h.hyphenize(node, '_', null)
		assert.equal('2 characters', node['_'])
	})

	test('parse invalid xml', function(done) {
		let conf = {}
		let h = new Hyphenizor('<', conf)
		h.parse().catch(function(e) {
			assert(e.message.match(/Unclosed root tag/))
			done()
		});
	})

	test('parse empty input', function(done) {
		let conf = {}
		let h = new Hyphenizor('', conf)
		h.parse().then(function(r) {
			assert.equal('', h.toString())
			done()
		}).catch(function(e) {
			console.log(e)
		})
	})

	test('parse too simple xml', function(done) {
		let conf = {}
		let h = new Hyphenizor('<foo>characters</foo>', conf)
		h.parse().catch(function(e) {
			assert(e.message.match(/too simple xml/))
			done()
		})
	})

	test('parse 01 no lang', function(done) {
		let conf = {}
		let h = new Hyphenizor('<foo>characters</foo>', conf)
		h.parse().catch(function(e) {
			assert(e.message.match(/no support for .+ lang/))
			done()
		})
	})

	test('parse 01', function(done) {
		let conf = {lang: 'en'}
		let h = new Hyphenizor('<foo>characters</foo>', conf)
		h.parse().then(function(r) {
//			console.log(h.toString())
			assert.equal(`<?xml version="1.0" encoding="UTF-8"?>

  <foo>characters</foo>
`, h.toString())
			done()
		}).catch(function(e) {
			console.log(e)
		})
	})

	test('parse 02', function(done) {
		let conf = {lang_detect: true}
		let h = new Hyphenizor('Данко<foo lang="en">characters</foo>', conf)
		h.parse().then(function(r) {
//			console.log(h.toString())
			assert.equal(`<?xml version="1.0" encoding="UTF-8"?>

  Данко
  <foo lang="en">characters</foo>
`, h.toString())
			done()
		}).catch(function(e) {
			console.log(e)
		})
	})

})
<|endoftext|>


<commit_before>function loadEnv(envFile) {
  require('fs').readFileSync(envFile)
    .toString()
    .split('\n')
    .forEach(function(line) {
      var segments = line.split('='),
          variable = segments[0],
          value = segments[1];

      if ( variable && value ) {
        process.env[variable] = value;
      }
    });
}

function ensureInEnv(variables) {
  variables.forEach(function(variable) {
    if ( !process.env[variable] ) {
      console.log(variable, 'is needed in .env.')
      process.exit(1);
    }
  });
}

function configure(grunt, templatePath, targetPath, data) {
  var template = grunt.file.read(templatePath),
      output = grunt.template.process(template, { data: data });

  if ( !grunt.file.exists(targetPath) || grunt.file.read(targetPath) !== output ) {
    grunt.file.write(targetPath, output);
    console.log('File "' + targetPath + '" created.');
  }
}

function configureAppTokens(grunt) {
  configure(grunt, 'app/index.html', 'tmp/index.html', {
    GOOGLE_ANALYTICS_TOKEN: process.env.GOOGLE_ANALYTICS_TOKEN,
    GOOGLE_ANALYTICS_HOST: process.env.GOOGLE_ANALYTICS_HOST,
    PARSE_APP_ID: process.env.PARSE_APP_ID,
    PARSE_JS_KEY: process.env.PARSE_JS_KEY
  });
}

function configureAdminTokens(grunt) {
  configure(grunt, 'admin/admin.html', 'tmp/admin.html', {
    PARSE_APP_ID: process.env.PARSE_APP_ID,
    PARSE_JS_KEY: process.env.PARSE_JS_KEY
  });
}

function configureMailgunTokens(grunt) {
  configure(grunt, 'server/cloud/cloud/mailgun_credentials.js.template', 'server/cloud/cloud/mailgun_credentials.js', {
    MAILGUN_DOMAIN: process.env.MAILGUN_DOMAIN,
    MAILGUN_API_KEY: process.env.MAILGUN_API_KEY,
    MAILGUN_TO_EMAIL_ADDRESS: process.env.MAILGUN_TO_EMAIL_ADDRESS,
    MAILGUN_FROM_EMAIL_ADDRESS: process.env.MAILGUN_FROM_EMAIL_ADDRESS,
  })
}

function configureGlobalJson(grunt) {
  configure(grunt, 'server/config/global.json.template', 'server/config/global.json', {
    PARSE_DEV_APP_ID:      process.env.PARSE_DEV_APP_ID,
    PARSE_DEV_MASTER_KEY:  process.env.PARSE_DEV_MASTER_KEY,
    PARSE_PROD_APP_ID:     process.env.PARSE_PROD_APP_ID,
    PARSE_PROD_MASTER_KEY: process.env.PARSE_PROD_MASTER_KEY
  });
}

module.exports = function(grunt) {
  grunt.registerTask('configure:development:app', 'Configure for the development app environment.', function() {
    loadEnv('.env');

    ensureInEnv([
      'MAILGUN_DOMAIN',
      'MAILGUN_API_KEY',
      'MAILGUN_TO_EMAIL_ADDRESS',
      'MAILGUN_FROM_EMAIL_ADDRESS',
      'PARSE_DEV_APP_ID',
      'PARSE_DEV_JS_KEY',
      'PARSE_DEV_MASTER_KEY',
      'GOOGLE_ANALYTICS_DEV_TOKEN',
      'GOOGLE_ANALYTICS_DEV_HOST'
    ]);

    process.env.PARSE_APP_ID = process.env.PARSE_DEV_APP_ID;
    process.env.PARSE_JS_KEY = process.env.PARSE_DEV_JS_KEY;
    process.env.GOOGLE_ANALYTICS_TOKEN = process.env.GOOGLE_ANALYTICS_DEV_TOKEN;
    process.env.GOOGLE_ANALYTICS_HOST = process.env.GOOGLE_ANALYTICS_DEV_HOST;

    configureGlobalJson(grunt);
    configureAppTokens(grunt);
    configureMailgunTokens(grunt);
  });

  grunt.registerTask('configure:development:admin', 'Configure for the development admin environment.', function() {
    loadEnv('.env');

    ensureInEnv([
      'PARSE_DEV_APP_ID',
      'PARSE_DEV_JS_KEY',
      'PARSE_DEV_MASTER_KEY'
    ]);

    process.env.PARSE_APP_ID = process.env.PARSE_DEV_APP_ID;
    process.env.PARSE_JS_KEY = process.env.PARSE_DEV_JS_KEY;

    configureGlobalJson(grunt);
    configureAdminTokens(grunt);
  });

  grunt.registerTask('configure:production', 'Configure for the production environment.', function() {
    loadEnv('.env');

    ensureInEnv([
      'MAILGUN_DOMAIN',
      'MAILGUN_API_KEY',
      'MAILGUN_TO_EMAIL_ADDRESS',
      'MAILGUN_FROM_EMAIL_ADDRESS',
      'PARSE_PROD_APP_ID',
      'PARSE_PROD_JS_KEY',
      'PARSE_PROD_MASTER_KEY',
      'GOOGLE_ANALYTICS_PROD_TOKEN',
      'GOOGLE_ANALYTICS_PROD_HOST'
    ]);

    process.env.PARSE_APP_ID = process.env.PARSE_PROD_APP_ID;
    process.env.PARSE_JS_KEY = process.env.PARSE_PROD_JS_KEY;
    process.env.GOOGLE_ANALYTICS_TOKEN = process.env.GOOGLE_ANALYTICS_PROD_TOKEN;
    process.env.GOOGLE_ANALYTICS_HOST = process.env.GOOGLE_ANALYTICS_PROD_HOST;

    configureGlobalJson(grunt);
    configureAppTokens(grunt);
    configureAdminTokens(grunt);
    configureMailgunTokens(grunt);
  });
};
<commit_msg>abort loadEnv if file doesn't exist<commit_after>function loadEnv(envFile) {
  var fs = require('fs');

  if ( !fs.existsSync(envFile) ) return;

  fs.readFileSync(envFile)
    .toString()
    .split('\n')
    .forEach(function(line) {
      var segments = line.split('='),
          variable = segments[0],
          value = segments[1];

      if ( variable && value ) {
        process.env[variable] = value;
      }
    });
}

function ensureInEnv(variables) {
  variables.forEach(function(variable) {
    if ( !process.env[variable] ) {
      console.log(variable, 'is needed in .env.')
      process.exit(1);
    }
  });
}

function configure(grunt, templatePath, targetPath, data) {
  var template = grunt.file.read(templatePath),
      output = grunt.template.process(template, { data: data });

  if ( !grunt.file.exists(targetPath) || grunt.file.read(targetPath) !== output ) {
    grunt.file.write(targetPath, output);
    console.log('File "' + targetPath + '" created.');
  }
}

function configureAppTokens(grunt) {
  configure(grunt, 'app/index.html', 'tmp/index.html', {
    GOOGLE_ANALYTICS_TOKEN: process.env.GOOGLE_ANALYTICS_TOKEN,
    GOOGLE_ANALYTICS_HOST: process.env.GOOGLE_ANALYTICS_HOST,
    PARSE_APP_ID: process.env.PARSE_APP_ID,
    PARSE_JS_KEY: process.env.PARSE_JS_KEY
  });
}

function configureAdminTokens(grunt) {
  configure(grunt, 'admin/admin.html', 'tmp/admin.html', {
    PARSE_APP_ID: process.env.PARSE_APP_ID,
    PARSE_JS_KEY: process.env.PARSE_JS_KEY
  });
}

function configureMailgunTokens(grunt) {
  configure(grunt, 'server/cloud/cloud/mailgun_credentials.js.template', 'server/cloud/cloud/mailgun_credentials.js', {
    MAILGUN_DOMAIN: process.env.MAILGUN_DOMAIN,
    MAILGUN_API_KEY: process.env.MAILGUN_API_KEY,
    MAILGUN_TO_EMAIL_ADDRESS: process.env.MAILGUN_TO_EMAIL_ADDRESS,
    MAILGUN_FROM_EMAIL_ADDRESS: process.env.MAILGUN_FROM_EMAIL_ADDRESS,
  })
}

function configureGlobalJson(grunt) {
  configure(grunt, 'server/config/global.json.template', 'server/config/global.json', {
    PARSE_DEV_APP_ID:      process.env.PARSE_DEV_APP_ID,
    PARSE_DEV_MASTER_KEY:  process.env.PARSE_DEV_MASTER_KEY,
    PARSE_PROD_APP_ID:     process.env.PARSE_PROD_APP_ID,
    PARSE_PROD_MASTER_KEY: process.env.PARSE_PROD_MASTER_KEY
  });
}

module.exports = function(grunt) {
  grunt.registerTask('configure:development:app', 'Configure for the development app environment.', function() {
    loadEnv('.env');

    ensureInEnv([
      'MAILGUN_DOMAIN',
      'MAILGUN_API_KEY',
      'MAILGUN_TO_EMAIL_ADDRESS',
      'MAILGUN_FROM_EMAIL_ADDRESS',
      'PARSE_DEV_APP_ID',
      'PARSE_DEV_JS_KEY',
      'PARSE_DEV_MASTER_KEY',
      'GOOGLE_ANALYTICS_DEV_TOKEN',
      'GOOGLE_ANALYTICS_DEV_HOST'
    ]);

    process.env.PARSE_APP_ID = process.env.PARSE_DEV_APP_ID;
    process.env.PARSE_JS_KEY = process.env.PARSE_DEV_JS_KEY;
    process.env.GOOGLE_ANALYTICS_TOKEN = process.env.GOOGLE_ANALYTICS_DEV_TOKEN;
    process.env.GOOGLE_ANALYTICS_HOST = process.env.GOOGLE_ANALYTICS_DEV_HOST;

    configureGlobalJson(grunt);
    configureAppTokens(grunt);
    configureMailgunTokens(grunt);
  });

  grunt.registerTask('configure:development:admin', 'Configure for the development admin environment.', function() {
    loadEnv('.env');

    ensureInEnv([
      'PARSE_DEV_APP_ID',
      'PARSE_DEV_JS_KEY',
      'PARSE_DEV_MASTER_KEY'
    ]);

    process.env.PARSE_APP_ID = process.env.PARSE_DEV_APP_ID;
    process.env.PARSE_JS_KEY = process.env.PARSE_DEV_JS_KEY;

    configureGlobalJson(grunt);
    configureAdminTokens(grunt);
  });

  grunt.registerTask('configure:production', 'Configure for the production environment.', function() {
    loadEnv('.env');

    ensureInEnv([
      'MAILGUN_DOMAIN',
      'MAILGUN_API_KEY',
      'MAILGUN_TO_EMAIL_ADDRESS',
      'MAILGUN_FROM_EMAIL_ADDRESS',
      'PARSE_PROD_APP_ID',
      'PARSE_PROD_JS_KEY',
      'PARSE_PROD_MASTER_KEY',
      'GOOGLE_ANALYTICS_PROD_TOKEN',
      'GOOGLE_ANALYTICS_PROD_HOST'
    ]);

    process.env.PARSE_APP_ID = process.env.PARSE_PROD_APP_ID;
    process.env.PARSE_JS_KEY = process.env.PARSE_PROD_JS_KEY;
    process.env.GOOGLE_ANALYTICS_TOKEN = process.env.GOOGLE_ANALYTICS_PROD_TOKEN;
    process.env.GOOGLE_ANALYTICS_HOST = process.env.GOOGLE_ANALYTICS_PROD_HOST;

    configureGlobalJson(grunt);
    configureAppTokens(grunt);
    configureAdminTokens(grunt);
    configureMailgunTokens(grunt);
  });
};
<|endoftext|>


<commit_before>'use strict';

var ejs = require('ejs');
var dateformat = require('dateformat');
var crypto = require('crypto');
var async = require('async');
var fs = require("fs");
var chalk = require('chalk');
var path = require('path');

var exec;

ejs.open = "{{";
ejs.close = "}}";

var cwd = __dirname;

var createFile = function (template, options) {
    var outerMetadata = fs.readFileSync(path.resolve(cwd, '../template/' + template)).toString();
    var metadata = ejs.render(outerMetadata, options);
    return metadata;
};

var md5 = function (str) {
    var hash = crypto.createHash('md5');
    return hash.update(str).digest('hex');
};

var sha1 = function (str) {
    var hash = crypto.createHash('sha1');
    return hash.update(str).digest('hex');
};

var save = function (fileContent, pomDir, fileName) {
    fs.writeFileSync(pomDir + '/' + fileName, fileContent);
    fs.writeFileSync(pomDir + '/' + fileName + '.md5', md5(fileContent));
    fs.writeFileSync(pomDir + '/' + fileName + '.sha1', sha1(fileContent));
};

var createAndUploadArtifacts = function (options, done) {
    var pomDir = options.pomDir || 'test/poms';

    options.parallel = options.parallel === undefined ? false : options.parallel;
    if (!fs.exists(pomDir)) {
        fs.mkdir(pomDir);
    }

    save(createFile('project-metadata.xml', options), pomDir, 'outer.xml');
    save(createFile('latest-metadata.xml', options), pomDir, 'inner.xml');
    save(createFile('pom.xml', options), pomDir, 'pom.xml');

    var artifactData = fs.readFileSync(options.artifact, {encoding: 'binary'});
    fs.writeFileSync(pomDir + '/artifact.' + options.packaging + '.md5', md5(artifactData));
    fs.writeFileSync(pomDir + '/artifact.' + options.packaging + '.sha1', sha1(artifactData));

    var upload = function (fileLocation, targetFile) {
        var uploadArtifact = function (cb) {
            var targetUri = options.url + '/' + targetFile, status;
            if (!options.quiet) {
                console.log(chalk.blue('Uploading to ' + targetUri + "\n\n"));
            }

            var curlOptions = [
                '--silent',
                '--output', '/dev/stderr',
                '--write-out', '"%{http_code}"',
                '--upload-file', fileLocation,
                '--noproxy', options.noproxy ? options.noproxy : '127.0.0.1'
            ];

            if (options.auth) {
                curlOptions.push('-u');
                curlOptions.push(options.auth.username + ":" + options.auth.password);
            }

            if (options.insecure) {
                curlOptions.push('--insecure');
            }

            var execOptions = {};
            options.cwd && (execOptions.cwd = options.cwd);

            var curlCmd = ['curl', curlOptions.join(' '), targetUri].join(' ');

            var childProcess = exec(curlCmd, execOptions, function () {
            });
            childProcess.stdout.on('data', function (data) {
                status = data;
            });
            childProcess.on('close', function (code) {
                if (code !== 0 || (status !== "200" && status !== "201")) {
                    cb("Status code " + status + " for " + targetUri, null);
                } else {
                    cb(null, "Ok");
                }
            });
        };
        return uploadArtifact;
    };

    var uploads = {};

    var groupIdAsPath = options.groupId.replace(/\./g, "/");
    var groupArtifactPath = groupIdAsPath + '/' + options.artifactId;

    uploads[pomDir + "/outer.xml"] = groupArtifactPath + '/' + 'maven-metadata.xml';
    uploads[pomDir + "/outer.xml.sha1"] = groupArtifactPath + '/' + 'maven-metadata.xml.sha1';
    uploads[pomDir + "/outer.xml.md5"] = groupArtifactPath + '/' + 'maven-metadata.xml.md5';

    var SNAPSHOT_VER = /.*SNAPSHOT$/i;

    var groupArtifactVersionPath = groupArtifactPath + '/' + options.version;
    if (SNAPSHOT_VER.test(options.version)) {
        uploads[pomDir + "/inner.xml"] = groupArtifactVersionPath + '/' + 'maven-metadata.xml';
        uploads[pomDir + "/inner.xml.sha1"] = groupArtifactVersionPath + '/' + 'maven-metadata.xml.sha1';
        uploads[pomDir + "/inner.xml.md5"] = groupArtifactVersionPath + '/' + 'maven-metadata.xml.md5';
    }

    var remoteArtifactName = options.artifactId + '-' + options.version;
    uploads[pomDir + "/pom.xml"] = groupArtifactVersionPath + '/' + remoteArtifactName + '.pom';
    uploads[pomDir + "/pom.xml.sha1"] = groupArtifactVersionPath + '/' + remoteArtifactName + '.pom.sha1';
    uploads[pomDir + "/pom.xml.md5"] = groupArtifactVersionPath + '/' + remoteArtifactName + '.pom.md5';

    if(options.classifier) {
        remoteArtifactName = remoteArtifactName + "-" + options.classifier;
    }
    uploads[options.artifact] = groupArtifactVersionPath + '/' + remoteArtifactName + '.' + options.packaging;
    uploads[pomDir + "/artifact." + options.packaging + ".sha1"] = groupArtifactVersionPath + '/' + remoteArtifactName + '.' + options.packaging + '.sha1';
    uploads[pomDir + "/artifact." + options.packaging + ".md5"] = groupArtifactVersionPath + '/' + remoteArtifactName + '.' + options.packaging + '.md5';

    var fns = [];
    for (var u in uploads) {
        if (uploads.hasOwnProperty(u)) {
            fns.push(upload(u, uploads[u]));
        }
    }

    var asyncFn = options.parallel ? async.parallel : async.series;
    asyncFn(fns, function (err) {
        if (!options.quiet) {
            console.log(chalk.blue('-------------------------------------------\n'));
            if (err) {
                console.log(chalk.red('Artifact Upload failed\n' + String(err)));
            } else {
                console.log(chalk.green('Artifacts uploaded successfully'));
            }
        }
        done(err ? false : true);
    });

};

module.exports = function (options, cb) {
    if (!options) {
        throw {name: "IllegalArgumentException", message: "upload artifact options required."};
    }
    exec = process.env.MOCK_NEXUS ? require('./mockexec') : require('child_process').exec;
    options.lastUpdated = process.env.MOCK_NEXUS ? '11111111111111': dateformat(new Date(), "yyyymmddHHMMss");
    createAndUploadArtifacts(options, cb);
};<commit_msg>removing misleading reporting of a non error<commit_after>'use strict';

var ejs = require('ejs');
var dateformat = require('dateformat');
var crypto = require('crypto');
var async = require('async');
var fs = require("fs");
var chalk = require('chalk');
var path = require('path');

var exec;

ejs.open = "{{";
ejs.close = "}}";

var cwd = __dirname;

var createFile = function (template, options) {
    var outerMetadata = fs.readFileSync(path.resolve(cwd, '../template/' + template)).toString();
    var metadata = ejs.render(outerMetadata, options);
    return metadata;
};

var md5 = function (str) {
    var hash = crypto.createHash('md5');
    return hash.update(str).digest('hex');
};

var sha1 = function (str) {
    var hash = crypto.createHash('sha1');
    return hash.update(str).digest('hex');
};

var save = function (fileContent, pomDir, fileName) {
    fs.writeFileSync(pomDir + '/' + fileName, fileContent);
    fs.writeFileSync(pomDir + '/' + fileName + '.md5', md5(fileContent));
    fs.writeFileSync(pomDir + '/' + fileName + '.sha1', sha1(fileContent));
};

var createAndUploadArtifacts = function (options, done) {
    var pomDir = options.pomDir || 'test/poms';

    options.parallel = options.parallel === undefined ? false : options.parallel;
    if (!fs.exists(pomDir)) {
        fs.mkdir(pomDir);
    }

    save(createFile('project-metadata.xml', options), pomDir, 'outer.xml');
    save(createFile('latest-metadata.xml', options), pomDir, 'inner.xml');
    save(createFile('pom.xml', options), pomDir, 'pom.xml');

    var artifactData = fs.readFileSync(options.artifact, {encoding: 'binary'});
    fs.writeFileSync(pomDir + '/artifact.' + options.packaging + '.md5', md5(artifactData));
    fs.writeFileSync(pomDir + '/artifact.' + options.packaging + '.sha1', sha1(artifactData));

    var upload = function (fileLocation, targetFile) {
        var uploadArtifact = function (cb) {
            var targetUri = options.url + '/' + targetFile, status;
            if (!options.quiet) {
                console.log(chalk.blue('Uploading to ' + targetUri + "\n\n"));
            }

            var curlOptions = [
                '--silent',
                '--output', '/dev/stderr',
                '--write-out', '"%{http_code}"',
                '--upload-file', fileLocation,
                '--noproxy', options.noproxy ? options.noproxy : '127.0.0.1'
            ];

            if (options.auth) {
                curlOptions.push('-u');
                curlOptions.push(options.auth.username + ":" + options.auth.password);
            }

            if (options.insecure) {
                curlOptions.push('--insecure');
            }

            var execOptions = {};
            options.cwd && (execOptions.cwd = options.cwd);

            var curlCmd = ['curl', curlOptions.join(' '), targetUri].join(' ');

            var childProcess = exec(curlCmd, execOptions, function () {
            });
            childProcess.stdout.on('data', function (data) {
                status = data;
            });
            childProcess.on('close', function (code) {
                if (code !== 0 || (status !== "200" && status !== "201")) {
                    cb("Status code " + status + " for " + targetUri, null);
                } else {
                    cb(null, "Ok");
                }
            });
        };
        return uploadArtifact;
    };

    var uploads = {};

    var groupIdAsPath = options.groupId.replace(/\./g, "/");
    var groupArtifactPath = groupIdAsPath + '/' + options.artifactId;

    uploads[pomDir + "/outer.xml"] = groupArtifactPath + '/' + 'maven-metadata.xml';
    uploads[pomDir + "/outer.xml.sha1"] = groupArtifactPath + '/' + 'maven-metadata.xml.sha1';
    uploads[pomDir + "/outer.xml.md5"] = groupArtifactPath + '/' + 'maven-metadata.xml.md5';

    var SNAPSHOT_VER = /.*SNAPSHOT$/i;

    var groupArtifactVersionPath = groupArtifactPath + '/' + options.version;
    if (SNAPSHOT_VER.test(options.version)) {
        uploads[pomDir + "/inner.xml"] = groupArtifactVersionPath + '/' + 'maven-metadata.xml';
        uploads[pomDir + "/inner.xml.sha1"] = groupArtifactVersionPath + '/' + 'maven-metadata.xml.sha1';
        uploads[pomDir + "/inner.xml.md5"] = groupArtifactVersionPath + '/' + 'maven-metadata.xml.md5';
    }

    var remoteArtifactName = options.artifactId + '-' + options.version;
    uploads[pomDir + "/pom.xml"] = groupArtifactVersionPath + '/' + remoteArtifactName + '.pom';
    uploads[pomDir + "/pom.xml.sha1"] = groupArtifactVersionPath + '/' + remoteArtifactName + '.pom.sha1';
    uploads[pomDir + "/pom.xml.md5"] = groupArtifactVersionPath + '/' + remoteArtifactName + '.pom.md5';

    if(options.classifier) {
        remoteArtifactName = remoteArtifactName + "-" + options.classifier;
    }
    uploads[options.artifact] = groupArtifactVersionPath + '/' + remoteArtifactName + '.' + options.packaging;
    uploads[pomDir + "/artifact." + options.packaging + ".sha1"] = groupArtifactVersionPath + '/' + remoteArtifactName + '.' + options.packaging + '.sha1';
    uploads[pomDir + "/artifact." + options.packaging + ".md5"] = groupArtifactVersionPath + '/' + remoteArtifactName + '.' + options.packaging + '.md5';

    var fns = [];
    for (var u in uploads) {
        if (uploads.hasOwnProperty(u)) {
            fns.push(upload(u, uploads[u]));
        }
    }

    var asyncFn = options.parallel ? async.parallel : async.series;
    asyncFn(fns, function (err) {
        if (!options.quiet) {
            console.log(chalk.blue('-------------------------------------------\n'));
            if (err) {
                console.log(chalk.red('Artifact Upload failed\n' + String(err)));
            } else {
                console.log(chalk.green('Artifacts uploaded successfully'));
            }
        }
        done(err);
    });

};

module.exports = function (options, cb) {
    if (!options) {
        throw {name: "IllegalArgumentException", message: "upload artifact options required."};
    }
    exec = process.env.MOCK_NEXUS ? require('./mockexec') : require('child_process').exec;
    options.lastUpdated = process.env.MOCK_NEXUS ? '11111111111111': dateformat(new Date(), "yyyymmddHHMMss");
    createAndUploadArtifacts(options, cb);
};<|endoftext|>


<commit_before>'use strict';

module.exports = function (grunt) {
  grunt.registerMultiTask('normalize', function () {
    var options = this.options();

    var path = require('path');
    var _ = require('lodash');
    var moment = require('moment');
    var helpers = require('./helpers');

    _.str = require('underscore.string');
    _.mixin(_.str.exports());

    var basePath = path.join(options.srcFolder, options.academicYear.replace('/', '-'), options.semester);
    var consolidatePath = path.join(basePath, grunt.config('consolidate').options.destFileName);
    var consolidated = grunt.file.readJSON(consolidatePath);

    var nullPattern = /^(--|n[/.]?a\.?|nil|none\.?|null|No Exam Date\.|)$/i;

    var facultyDepartments = {};

    var dayTextToCode = {
      MONDAY: '1',
      TUESDAY: '2',
      WEDNESDAY: '3',
      THURSDAY: '4',
      FRIDAY: '5',
      SATURDAY: '6',
      SUNDAY: '7'
    };

    var modules = _.compact(_.map(consolidated, function (rawMod) {
      var modInfo = rawMod.Bulletin || rawMod.CORS;
      if (modInfo) {
        var mod = _.pick(modInfo, 'ModuleCode', 'ModuleTitle', 'Department',
          'ModuleDescription', 'CrossModule', 'ModuleCredit', 'Workload',
          'Prerequisite', 'Preclusion', 'Corequisite');
        _.each(mod, function (value, key) {
          value = mod[key] = _.clean(value);
          if (nullPattern.test(value)) {
            delete mod[key];
          }
        });

        if (rawMod.Bulletin) {
          facultyDepartments[modInfo.Faculty] = facultyDepartments[modInfo.Faculty] || {};
          facultyDepartments[modInfo.Faculty][modInfo.Department] = true;
        }

        var exam = rawMod.Exam;
        if (exam) {
          mod.ExamDate = moment.utc(exam.Date.slice(0, 11) + exam.Time,
            'DD/MM/YYYY h:mm a').toISOString().slice(0, 16) + '+0800';
          if (exam[''] === '*') {
            mod.ExamOpenBook = true;
          }
          if (exam.Duration) {
            mod.ExamDuration = 'P' + exam.Duration.replace(/\s/g, '').toUpperCase().slice(0, 5);
          }
          if (exam.Venue) {
            mod.ExamVenue = exam.Venue;
          }
        } else if (rawMod.CORS) {
          exam = rawMod.CORS.ExamDate;
          if (exam !== 'No Exam Date.') {
            var dateTime = rawMod.CORS.ExamDate.split(' ');
            var date = moment.utc(dateTime[0], 'DD-MM-YYYY');
            switch (dateTime[1]) {
              case 'AM':
                date.hour(9);
                break;
              case 'PM':
                // 2.30 PM on Friday afternoons
                if (date.day() === 5) {
                  date.hour(14).minute(30);
                } else {
                  date.hour(13);
                }
                break;
              case 'EVENING':
                date.hour(17);
                break;
              default:
                grunt.fail.warn('Unexpected exam time ' + dateTime[1]);
            }
            mod.ExamDate = date.toISOString().slice(0, 16) + '+0800';
          }
        }

        if (rawMod.CORS) {
          mod.Types = rawMod.CORS.Types;
        }

        if (rawMod.IVLE) {
          if (rawMod.IVLE[0]) {
            mod.Lecturers = _.compact(rawMod.IVLE[0].Lecturers.map(function (lecturer) {
              switch (lecturer.Role.trim()) {
                case 'Lecturer':
                case 'Co-Lecturer':
                case 'Visiting Professor':
                  return lecturer.User.Name;
              }
            }));
          }
          mod.IVLE = rawMod.IVLE;
        }

        if (rawMod.CORS && rawMod.CORS.Timetable.length) {
          mod.Timetable = rawMod.CORS.Timetable.map(function (lesson) {
            lesson.DayCode = dayTextToCode[lesson.DayText];
            lesson.Venue = lesson.Venue.replace(/(?:^null)?,$/, '');
            return lesson;
          });
        } else {
          _.each(rawMod.TimetableDelta, function (delta) {
            // Ignore Sundays - they seem to be dummy values.
            if (delta.DayCode === '7') {
              return;
            }

            var timetable = mod.Timetable = mod.Timetable || [];
            var originalLength = timetable.length;
            for (var i = originalLength; i--;) {
              var lesson = timetable[i];
              if (lesson.ClassNo === delta.ClassNo &&
                lesson.LessonType === delta.LessonType &&
                lesson.WeekText === delta.WeekText &&
                lesson.DayCode === delta.DayCode &&
                lesson.StartTime === delta.StartTime &&
                lesson.EndTime === delta.EndTime) {
                if (lesson.Venue === delta.Venue || (!delta.isDelete &&
                  lesson.LastModified_js !== delta.LastModified_js)) {
                  timetable.splice(i, 1);
                }
              }
            }
            var lessonsDeleted = originalLength - timetable.length;
            if (delta.isDelete) {
              if (lessonsDeleted !== 1) {
                grunt.verbose.writeln(lessonsDeleted + ' lessons deleted for ' + modInfo.ModuleCode);
              }
              if (timetable.length === 0) {
                grunt.verbose.writeln('No more lessons for ' + modInfo.ModuleCode);
                delete mod.Timetable;
              }
            } else {
              if (lessonsDeleted > 0) {
                grunt.verbose.writeln('Duplicate lesson deleted for ' + modInfo.ModuleCode);
              }
              timetable.push(_.pick(delta, 'ClassNo', 'LessonType', 'WeekText', 'DayCode',
                'DayText', 'StartTime', 'EndTime', 'Venue', 'LastModified_js'));
            }
          });
        }

        if (rawMod.CorsBiddingStats) {
          mod.CorsBiddingStats = rawMod.CorsBiddingStats;
        }

        if (mod.Timetable) {
          mod.Timetable.forEach(function (lesson) {
            lesson.DayText = lesson.DayText.toUpperCase();
            lesson.StartTime = ('000' + lesson.StartTime).slice(-4);
            lesson.EndTime = ('000' + lesson.EndTime).slice(-4);
            lesson.Venue = lesson.Venue.trim();
          });

          var lessonSortOrder = ['LessonType', 'ClassNo', 'DayCode', 'StartTime',
            'EndTime', 'WeekText', 'Venue'];
          mod.Timetable.sort(function (a, b) {
            for (var i = 0; i < lessonSortOrder.length; i++) {
              var key = lessonSortOrder[i];
              if (a[key] !== b[key]) {
                return a[key] > b[key] ? 1 : -1;
              }
            }
            return 0;
          });

          mod.Timetable.forEach(function (lesson) {
            delete lesson.DayCode;
            delete lesson.LastModified_js;
          });
        }

        return mod;
      }
    }));

    _.each(facultyDepartments, function (departments, faculty) {
      facultyDepartments[faculty] = Object.keys(departments).sort();
    });
    grunt.file.write(
      path.join(basePath, options.destFacultyDepartments),
      JSON.stringify(helpers.sortByKey(facultyDepartments), null, options.jsonSpace)
    );

    grunt.file.write(
      path.join(basePath, options.destFileName),
      JSON.stringify(modules, null, options.jsonSpace)
    );
  });
};
<commit_msg>Omit module code and faculty from cors bidding stats<commit_after>'use strict';

module.exports = function (grunt) {
  grunt.registerMultiTask('normalize', function () {
    var options = this.options();

    var path = require('path');
    var _ = require('lodash');
    var moment = require('moment');
    var helpers = require('./helpers');

    _.str = require('underscore.string');
    _.mixin(_.str.exports());

    var basePath = path.join(options.srcFolder, options.academicYear.replace('/', '-'), options.semester);
    var consolidatePath = path.join(basePath, grunt.config('consolidate').options.destFileName);
    var consolidated = grunt.file.readJSON(consolidatePath);

    var nullPattern = /^(--|n[/.]?a\.?|nil|none\.?|null|No Exam Date\.|)$/i;

    var facultyDepartments = {};

    var dayTextToCode = {
      MONDAY: '1',
      TUESDAY: '2',
      WEDNESDAY: '3',
      THURSDAY: '4',
      FRIDAY: '5',
      SATURDAY: '6',
      SUNDAY: '7'
    };

    var modules = _.compact(_.map(consolidated, function (rawMod) {
      var modInfo = rawMod.Bulletin || rawMod.CORS;
      if (modInfo) {
        var mod = _.pick(modInfo, 'ModuleCode', 'ModuleTitle', 'Department',
          'ModuleDescription', 'CrossModule', 'ModuleCredit', 'Workload',
          'Prerequisite', 'Preclusion', 'Corequisite');
        _.each(mod, function (value, key) {
          value = mod[key] = _.clean(value);
          if (nullPattern.test(value)) {
            delete mod[key];
          }
        });

        if (rawMod.Bulletin) {
          facultyDepartments[modInfo.Faculty] = facultyDepartments[modInfo.Faculty] || {};
          facultyDepartments[modInfo.Faculty][modInfo.Department] = true;
        }

        var exam = rawMod.Exam;
        if (exam) {
          mod.ExamDate = moment.utc(exam.Date.slice(0, 11) + exam.Time,
            'DD/MM/YYYY h:mm a').toISOString().slice(0, 16) + '+0800';
          if (exam[''] === '*') {
            mod.ExamOpenBook = true;
          }
          if (exam.Duration) {
            mod.ExamDuration = 'P' + exam.Duration.replace(/\s/g, '').toUpperCase().slice(0, 5);
          }
          if (exam.Venue) {
            mod.ExamVenue = exam.Venue;
          }
        } else if (rawMod.CORS) {
          exam = rawMod.CORS.ExamDate;
          if (exam !== 'No Exam Date.') {
            var dateTime = rawMod.CORS.ExamDate.split(' ');
            var date = moment.utc(dateTime[0], 'DD-MM-YYYY');
            switch (dateTime[1]) {
              case 'AM':
                date.hour(9);
                break;
              case 'PM':
                // 2.30 PM on Friday afternoons
                if (date.day() === 5) {
                  date.hour(14).minute(30);
                } else {
                  date.hour(13);
                }
                break;
              case 'EVENING':
                date.hour(17);
                break;
              default:
                grunt.fail.warn('Unexpected exam time ' + dateTime[1]);
            }
            mod.ExamDate = date.toISOString().slice(0, 16) + '+0800';
          }
        }

        if (rawMod.CORS) {
          mod.Types = rawMod.CORS.Types;
        }

        if (rawMod.IVLE) {
          if (rawMod.IVLE[0]) {
            mod.Lecturers = _.compact(rawMod.IVLE[0].Lecturers.map(function (lecturer) {
              switch (lecturer.Role.trim()) {
                case 'Lecturer':
                case 'Co-Lecturer':
                case 'Visiting Professor':
                  return lecturer.User.Name;
              }
            }));
          }
          mod.IVLE = rawMod.IVLE;
        }

        if (rawMod.CORS && rawMod.CORS.Timetable.length) {
          mod.Timetable = rawMod.CORS.Timetable.map(function (lesson) {
            lesson.DayCode = dayTextToCode[lesson.DayText];
            lesson.Venue = lesson.Venue.replace(/(?:^null)?,$/, '');
            return lesson;
          });
        } else {
          _.each(rawMod.TimetableDelta, function (delta) {
            // Ignore Sundays - they seem to be dummy values.
            if (delta.DayCode === '7') {
              return;
            }

            var timetable = mod.Timetable = mod.Timetable || [];
            var originalLength = timetable.length;
            for (var i = originalLength; i--;) {
              var lesson = timetable[i];
              if (lesson.ClassNo === delta.ClassNo &&
                lesson.LessonType === delta.LessonType &&
                lesson.WeekText === delta.WeekText &&
                lesson.DayCode === delta.DayCode &&
                lesson.StartTime === delta.StartTime &&
                lesson.EndTime === delta.EndTime) {
                if (lesson.Venue === delta.Venue || (!delta.isDelete &&
                  lesson.LastModified_js !== delta.LastModified_js)) {
                  timetable.splice(i, 1);
                }
              }
            }
            var lessonsDeleted = originalLength - timetable.length;
            if (delta.isDelete) {
              if (lessonsDeleted !== 1) {
                grunt.verbose.writeln(lessonsDeleted + ' lessons deleted for ' + modInfo.ModuleCode);
              }
              if (timetable.length === 0) {
                grunt.verbose.writeln('No more lessons for ' + modInfo.ModuleCode);
                delete mod.Timetable;
              }
            } else {
              if (lessonsDeleted > 0) {
                grunt.verbose.writeln('Duplicate lesson deleted for ' + modInfo.ModuleCode);
              }
              timetable.push(_.pick(delta, 'ClassNo', 'LessonType', 'WeekText', 'DayCode',
                'DayText', 'StartTime', 'EndTime', 'Venue', 'LastModified_js'));
            }
          });
        }

        if (rawMod.CorsBiddingStats) {
          mod.CorsBiddingStats = _.map(rawMod.CorsBiddingStats, function (stats) {
            return _.omit(stats, 'ModuleCode', 'Faculty');
          });
        }

        if (mod.Timetable) {
          mod.Timetable.forEach(function (lesson) {
            lesson.DayText = lesson.DayText.toUpperCase();
            lesson.StartTime = ('000' + lesson.StartTime).slice(-4);
            lesson.EndTime = ('000' + lesson.EndTime).slice(-4);
            lesson.Venue = lesson.Venue.trim();
          });

          var lessonSortOrder = ['LessonType', 'ClassNo', 'DayCode', 'StartTime',
            'EndTime', 'WeekText', 'Venue'];
          mod.Timetable.sort(function (a, b) {
            for (var i = 0; i < lessonSortOrder.length; i++) {
              var key = lessonSortOrder[i];
              if (a[key] !== b[key]) {
                return a[key] > b[key] ? 1 : -1;
              }
            }
            return 0;
          });

          mod.Timetable.forEach(function (lesson) {
            delete lesson.DayCode;
            delete lesson.LastModified_js;
          });
        }

        return mod;
      }
    }));

    _.each(facultyDepartments, function (departments, faculty) {
      facultyDepartments[faculty] = Object.keys(departments).sort();
    });
    grunt.file.write(
      path.join(basePath, options.destFacultyDepartments),
      JSON.stringify(helpers.sortByKey(facultyDepartments), null, options.jsonSpace)
    );

    grunt.file.write(
      path.join(basePath, options.destFileName),
      JSON.stringify(modules, null, options.jsonSpace)
    );
  });
};
<|endoftext|>


<commit_before>'use strict';

module.exports = function(grunt) {

	var wd = require('wd');
	var Batch = require('batch');
	var request = require('request');
	var SauceTunnel = require('sauce-tunnel');
	var mkdirp = require('mkdirp');
	var path = require('path');
	var fs = require('fs');
	var testResultsPath = path.join(__dirname, '../test/results');
	var UA = require('../lib/UA');

	var pollTick = 1000;
	var retryLimit = 10;
	var sauceConnectPort = 4445;

	grunt.registerMultiTask('saucelabs', 'Perform tests on Sauce', function() {
		var gruntDone = this.async();
		var nameSuffix = process.env.TRAVIS_BUILD_NUMBER ? "-travis-build:" + process.env.TRAVIS_BUILD_NUMBER : ':' + (new Date()).toUTCString();
		var tunnelId = 'SauceTunnel-' + nameSuffix;

		if (!process.env.SAUCE_USER_NAME || !process.env.SAUCE_API_KEY) {
			gruntDone(new Error('SAUCE_USER_NAME and SAUCE_API_KEY must be set in the environment'));
		}

		var options = this.options({
			name: 'grunt-test' + nameSuffix,
			build: null,
			username: process.env.SAUCE_USER_NAME,
			key: process.env.SAUCE_API_KEY,
			browsers: [],
			url: {},
			concurrency: 3,
			tags: [],
			video: true,
			screenshots: true,
			cibuild: false
		});

		var batch = new Batch();
		batch.concurrency(options.concurrency);

		var tunnel = new SauceTunnel(options.username, options.key, tunnelId, true);

		// Create a new job to pass into the Batch runner, returns a function
		function newTestJob(url, urlName, conf) {
			return function testJob(done) {

				// Variadic like console.log(...);
				function log() {
					var leader = (new Date()).toString() + ' ' + conf.browserName + "/" + conf.version + ": ";
					var args = Array.prototype.slice.call(arguments);
					args.unshift(leader);
					console.log.apply(null, args);
				}

				log("started job");

				var browser = wd.remote("127.0.0.1", sauceConnectPort, options.username, options.key);

				log("requested browser");

				browser.init(conf, function() {

					log("browser launched");

					browser.get(url, function(err) {
						var refreshed = false;
						var remainingCount = null;
						var retryCount = 0;

						if (err) {
							log(err);
							return done(err);
						}

						browser.refresh(function() {
							refreshed = true;
							log("kicked");
							setTimeout(waitOnResults, pollTick);
						});

						// Wait until results are available
						function waitOnResults() {
							browser.eval('window.global_test_results ||  window.remainingCount', function(err, data) {

								if (typeof data !== 'object') {
									if (retryCount > retryLimit) {
										log('Timed out');
										browser.quit();
										done(null, { status: 'failed', uaString: (typeof data == 'object') ? (data.uaString || '??') : '??', id: browser.sessionID, name: conf.browserName, version: conf.version, failed: '??', total: '??'});
									} else {
										if (typeof data === 'number' && data !== remainingCount) {
											remainingCount = data;
											retryCount = 0;
											log(remainingCount + ' test pages remaining');
										} else {
											retryCount++;
											log('no changes, waiting');
										}
										setTimeout(waitOnResults, pollTick);
									}
									return;
								}

								log("results received, closing remote browser");

								// Close the browser as soon as we have the
								// results.  No need to keep it hanging around
								// until the rest of the job is complete.
								browser.quit();

								process.nextTick(function() {
									done(null, {
										id: browser.sessionID,
										browserName: conf.browserName,
										version: conf.version,
										urlName: urlName,
										results: data
									});
								});

								request({
									method: "PUT",
									uri: ["https://", options.username, ":", options.key, "@saucelabs.com/rest", "/v1/", options.username, "/jobs/", browser.sessionID].join(''),
									headers: {'Content-Type': 'application/json'},
									body: JSON.stringify({
										'custom-data': { custom: data },
										'passed': !data.failed
									})
								});
							});
						};
					});
				});
			};
		}

		function readResultsFile(cb) {
			var file = path.join(testResultsPath, 'results.json');
			mkdirp(testResultsPath, function(err) {
				if (err) {
					grunt.warn('Cannot write test results - '+testResultsPath+' cannot be written');
					cb({});
				} else {
					fs.readFile(file, function(err, filedata) {
						if (err) {
							// If ENOENT, continue as writeFile will create it later
							if (err.code !== 'ENOENT') {
								throw err;
							}
						}
						cb(filedata ? JSON.parse(filedata) : {});
					});
				}
			});
		}

		function writeResultsFile(data) {
			var file = path.join(testResultsPath, 'results.json');
			fs.writeFile(file, JSON.stringify(data, null, 2), function(err) {
				if (err) {
					console.log(err);
					throw err;
				}
			});
		}

		options.browsers.forEach(function(conf) {
			conf['name'] = options.name;
			conf['record-video'] = options.video;
			conf['record-screenshots'] = options.screenshots;
			conf['tunnel-identifier'] = tunnelId;
			if (options.tags) conf['tags'] = options.tags;
			if (options.build) conf['build'] = options.build;

			Object.keys(options.urls).forEach(function(urlName) {
				var url = options.urls[urlName];
				batch.push(newTestJob(url, urlName, conf));
				console.log('Adding batch job for '+options.name+', '+urlName+' on '+conf.browserName+' '+conf.version);
			});
		});

		readResultsFile(function(testResults) {

			grunt.log.writeln('Opening tunnel to Sauce Labs');
			tunnel.start(function(status) {

				grunt.log.writeln("Tunnel Started");
				if (status !== true)  {
					gruntDone(status);
				}

				grunt.log.writeln("Starting test jobs");
				batch.end(function(err, jobresults) {
					grunt.log.writeln('Jobs complete');
					tunnel.stop(function() {
						var passingUAs = [];
						var failed = false;
						grunt.log.writeln('Sauce tunnel stopped');
						if (err) {
							gruntDone(err);
							return;
						}
						grunt.log.writeln("Failed tests:")
						jobresults.forEach(function(job) {
							if (job.results.failed && job.results.failingSuites) {
								grunt.log.writeln(' - '+job.browserName+' '+job.version+' (Sauce results: https://saucelabs.com/tests/' + job.id+')')
								Object.keys(job.results.failingSuites).forEach(function(feature) {
									var url = options.urls[job.urlName].replace(/test\/director/, 'test/tests')+'&feature='+feature;
									grunt.log.writeln('    -> '+feature);
									grunt.log.writeln('       '+url);
								});
								failed = true;
							} else {
								passingUAs.push(job.browserName+'/'+job.version);
							}

							var browserName = UA.normalizeName(job.browserName) || job.browserName;

							if (!testResults[browserName]) {
								testResults[browserName] = {};
							}

							if (!testResults[browserName][job.version]) {
								testResults[browserName][job.version]	= {};
							}

							testResults[browserName][job.version][job.urlName] = {
								passed: job.results.passed,
								failed: job.results.failed,
								failingSuites: job.results.failingSuites ? Object.keys(job.results.failingSuites) : [],
								testedSuites: job.results.testedSuites
							};
						});
						if (passingUAs.length) {
							console.log('No failures in: '+passingUAs.join(', '));
						}

						writeResultsFile(testResults);

						process.nextTick(function() {

							// Always report the grunt task as successful if not running as CI
							if (!options.cibuild) {
								gruntDone(null, true);
							} else {
								gruntDone(!failed, failed);
							}
						});
					});
				});
			});
		});
	});
};
<commit_msg>Add some extra logging to saucelabs task.<commit_after>'use strict';

module.exports = function(grunt) {

	var wd = require('wd');
	var Batch = require('batch');
	var request = require('request');
	var SauceTunnel = require('sauce-tunnel');
	var mkdirp = require('mkdirp');
	var path = require('path');
	var fs = require('fs');
	var testResultsPath = path.join(__dirname, '../test/results');
	var UA = require('../lib/UA');

	var pollTick = 1000;
	var retryLimit = 10;
	var sauceConnectPort = 4445;

	var jobsCalledDone = [];

	grunt.registerMultiTask('saucelabs', 'Perform tests on Sauce', function() {
		var gruntDone = this.async();
		var nameSuffix = process.env.TRAVIS_BUILD_NUMBER ? "-travis-build:" + process.env.TRAVIS_BUILD_NUMBER : ':' + (new Date()).toUTCString();
		var tunnelId = 'SauceTunnel-' + nameSuffix;

		if (!process.env.SAUCE_USER_NAME || !process.env.SAUCE_API_KEY) {
			gruntDone(new Error('SAUCE_USER_NAME and SAUCE_API_KEY must be set in the environment'));
		}

		var options = this.options({
			name: 'grunt-test' + nameSuffix,
			build: null,
			username: process.env.SAUCE_USER_NAME,
			key: process.env.SAUCE_API_KEY,
			browsers: [],
			url: {},
			concurrency: 3,
			tags: [],
			video: true,
			screenshots: true,
			cibuild: false
		});

		var batch = new Batch();
		batch.concurrency(options.concurrency);

		var tunnel = new SauceTunnel(options.username, options.key, tunnelId, true);

		// Create a new job to pass into the Batch runner, returns a function
		function newTestJob(url, urlName, conf) {
			return function testJob(done) {

				// Variadic like console.log(...);
				function log() {
					var leader = (new Date()).toString() + ' ' + conf.browserName + "/" + conf.version + ": ";
					var args = Array.prototype.slice.call(arguments);
					args.unshift(leader);
					console.log.apply(null, args);
				}

				log("started job");

				var browser = wd.remote("127.0.0.1", sauceConnectPort, options.username, options.key);

				log("requested browser");

				browser.init(conf, function() {

					log("browser launched");

					browser.get(url, function(err) {
						var refreshed = false;
						var remainingCount = null;
						var retryCount = 0;

						if (err) {
							log(err);
							return done(err);
						}

						browser.refresh(function() {
							refreshed = true;
							log("kicked");
							setTimeout(waitOnResults, pollTick);
						});

						// Wait until results are available
						function waitOnResults() {
							browser.eval('window.global_test_results ||  window.remainingCount', function(err, data) {

								if (typeof data !== 'object') {
									if (retryCount > retryLimit) {
										log('Timed out');
										browser.quit();
										done(null, { status: 'failed', uaString: (typeof data == 'object') ? (data.uaString || '??') : '??', id: browser.sessionID, name: conf.browserName, version: conf.version, failed: '??', total: '??'});
									} else {
										if (typeof data === 'number' && data !== remainingCount) {
											remainingCount = data;
											retryCount = 0;
											log(remainingCount + ' test pages remaining');
										} else {
											retryCount++;
											log('no changes, waiting');
										}
										setTimeout(waitOnResults, pollTick);
									}
									return;
								}

								log("results received, closing remote browser");

								// Close the browser as soon as we have the
								// results.  No need to keep it hanging around
								// until the rest of the job is complete.
								browser.quit();
								log("Browser closing");

								process.nextTick(function() {
									log('calling done');
									jobsCalledDone.push(conf.browserName + '/' + conf.version);
									done(null, {
										id: browser.sessionID,
										browserName: conf.browserName,
										version: conf.version,
										urlName: urlName,
										results: data
									});
								});

								log("updating sauce with the test results");
								request({
									method: "PUT",
									uri: ["https://", options.username, ":", options.key, "@saucelabs.com/rest", "/v1/", options.username, "/jobs/", browser.sessionID].join(''),
									headers: {'Content-Type': 'application/json'},
									body: JSON.stringify({
										'custom-data': { custom: data },
										'passed': !data.failed
									})
								});
							});
						};
					});
				});
			};
		}

		function readResultsFile(cb) {
			var file = path.join(testResultsPath, 'results.json');
			mkdirp(testResultsPath, function(err) {
				if (err) {
					grunt.warn('Cannot write test results - '+testResultsPath+' cannot be written');
					cb({});
				} else {
					fs.readFile(file, function(err, filedata) {
						if (err) {
							// If ENOENT, continue as writeFile will create it later
							if (err.code !== 'ENOENT') {
								throw err;
							}
						}
						cb(filedata ? JSON.parse(filedata) : {});
					});
				}
			});
		}

		function writeResultsFile(data) {
			var file = path.join(testResultsPath, 'results.json');
			fs.writeFile(file, JSON.stringify(data, null, 2), function(err) {
				if (err) {
					console.log(err);
					throw err;
				}
			});
		}

		options.browsers.forEach(function(conf) {
			conf['name'] = options.name;
			conf['record-video'] = options.video;
			conf['record-screenshots'] = options.screenshots;
			conf['tunnel-identifier'] = tunnelId;
			if (options.tags) conf['tags'] = options.tags;
			if (options.build) conf['build'] = options.build;

			Object.keys(options.urls).forEach(function(urlName) {
				var url = options.urls[urlName];
				batch.push(newTestJob(url, urlName, conf));
				console.log('Adding batch job for '+options.name+', '+urlName+' on '+conf.browserName+' '+conf.version);
			});
		});

		readResultsFile(function(testResults) {

			grunt.log.writeln('Opening tunnel to Sauce Labs');
			tunnel.start(function(status) {

				grunt.log.writeln("Tunnel Started");
				if (status !== true)  {
					gruntDone(status);
				}

				batch.on('progress', function(e){
					console.log("Pending: " + e.pending);
					console.log("Job Completed: " + jobsCalledDone.length);
				});

				grunt.log.writeln("Starting test jobs");

				batch.end(function(err, jobresults) {
					grunt.log.writeln('Jobs complete');
					tunnel.stop(function() {
						var passingUAs = [];
						var failed = false;
						grunt.log.writeln('Sauce tunnel stopped');
						if (err) {
							gruntDone(err);
							return;
						}
						grunt.log.writeln("Failed tests:")
						jobresults.forEach(function(job) {
							if (job.results.failed && job.results.failingSuites) {
								grunt.log.writeln(' - '+job.browserName+' '+job.version+' (Sauce results: https://saucelabs.com/tests/' + job.id+')')
								Object.keys(job.results.failingSuites).forEach(function(feature) {
									var url = options.urls[job.urlName].replace(/test\/director/, 'test/tests')+'&feature='+feature;
									grunt.log.writeln('    -> '+feature);
									grunt.log.writeln('       '+url);
								});
								failed = true;
							} else {
								passingUAs.push(job.browserName+'/'+job.version);
							}

							var browserName = UA.normalizeName(job.browserName) || job.browserName;

							if (!testResults[browserName]) {
								testResults[browserName] = {};
							}

							if (!testResults[browserName][job.version]) {
								testResults[browserName][job.version]	= {};
							}

							testResults[browserName][job.version][job.urlName] = {
								passed: job.results.passed,
								failed: job.results.failed,
								failingSuites: job.results.failingSuites ? Object.keys(job.results.failingSuites) : [],
								testedSuites: job.results.testedSuites
							};
						});
						if (passingUAs.length) {
							console.log('No failures in: '+passingUAs.join(', '));
						}

						writeResultsFile(testResults);

						process.nextTick(function() {

							// Always report the grunt task as successful if not running as CI
							if (!options.cibuild) {
								gruntDone(null, true);
							} else {
								gruntDone(!failed, failed);
							}
						});
					});
				});
			});
		});
	});
};
<|endoftext|>