<commit_before>import React from 'react';
import Relay from 'react-relay';
import shallowCompare from 'react-addons-shallow-compare';

import Dropdown from '../shared/Dropdown';
import Icon from '../shared/Icon';
import Spinner from '../shared/Spinner';
import UserAvatar from '../shared/UserAvatar';

import FlashesStore from '../../stores/FlashesStore';

import EmailCreateMutation from '../../mutations/EmailCreate';
import NoticeDismissMutation from '../../mutations/NoticeDismiss';

class AvatarWithUnknownEmailPrompt extends React.Component {
  static propTypes = {
    build: React.PropTypes.shape({
      createdBy: React.PropTypes.shape({
        email: React.PropTypes.string,
        name: React.PropTypes.string
      })
    }).isRequired,
    viewer: React.PropTypes.shape({
      emails: React.PropTypes.shape({
        edges: React.PropTypes.arrayOf(
          React.PropTypes.shape({
            node: React.PropTypes.shape({
              id: React.PropTypes.string,
              address: React.PropTypes.string,
              verified: React.PropTypes.bool
            })
          })
        )
      }).isRequired,
      notice: React.PropTypes.shape({
        dismissedAt: React.PropTypes.string
      })
    }).isRequired,
    relay: React.PropTypes.object
  };

  state = {
    isAddingEmail: false,
    isDismissing: false,
    hasSentSomething: false,
    hasBeenDismissed: false
  };

  getUserEmail(email) {
    const userEmails = this.props.viewer.emails.edges;

    const userEmail = userEmails.find(
      ({ node: { address: userEmail } }) => (
        userEmail.toLowerCase() === email.toLowerCase()
      )
    );

    if (userEmail) {
      return userEmail.node;
    }
  }

  isCurrentUsersValidatedEmail(email) {
    const userEmail = this.getUserEmail(email);
    return userEmail && userEmail.verified;
  }

  shouldComponentUpdate(nextProps, nextState) {
    return shallowCompare(this, nextProps, nextState);
  }

  componentDidMount() {
    const { createdBy } = this.props.build;

    if (createdBy && createdBy.email && createdBy.__typename === "UnregisteredUser" && !this.isCurrentUsersValidatedEmail(createdBy.email)) {
      this.props.relay.setVariables(
        {
          isTryingToPrompt: true,
          emailForPrompt: createdBy.email.toLowerCase()
        },
        (readyState) => {
          if (readyState.done) {
            setTimeout(() => {
              const { dismissedAt } = this.props.viewer.notice;

              if (this._dropdown && !dismissedAt) {
                this._dropdown.setShowing(true);
              }
            }, 0);
          }
        }
      );
    }
  }

  getMessages(loading) {
    // There won't be an email address if this build was created by a
    // registered user or if this build just has no owner (perhaps it was
    // created by Buildkite)
    if (!this.props.build.createdBy.email) {
      return {};
    }

    // If we haven't decided to send a query for this yet, don't show anything!
    if (!this.props.relay.variables.isTryingToPrompt) {
      return {};
    }

    let message;
    let buttons;

    const userEmail = this.getUserEmail(this.props.build.createdBy.email);

    if (!this.state.isAddingEmail && userEmail) {
      if (userEmail.verified) {
        return {};
      }

      message = (

Email verification needed

We’ve sent a verification email to {this.props.build.createdBy.email}. Click the link in that email to finish adding it to your account.

You can resend the verification email or remove this email address in your Personal Email Settings

      );
    } else {
      // Otherwise, we've got an unknown (to Buildkite) email address on our hands!
      message = (

Unknown build commit email

The email {this.props.build.createdBy.email} could not be matched to any users in your organization. If this email address belongs to you, add it to your personal list of email addresses.

      );
      buttons = [
        <button
          key="add-email"
          className="btn btn-primary flex-auto m1"
          onClick={this.handleAddEmailClick}
          disabled={loading}
        >
          Add Email Address
        </button>,
        <button
          key="dismiss-email"
          className="btn btn-outline border-gray flex-auto m1"
          onClick={this.handleDismissClick}
          disabled={loading}
        >
          Not My Email Address
        </button>
      ];
    }

    return { message, buttons, loading };
  }

  isLoading = () => (
    this.state.isAddingEmail || this.state.isDismissing
  );

  render() {
    const loading = this.isLoading();

    const { message, buttons } = this.getMessages(loading);
    let buttonContent;

    if (buttons) {
      buttonContent = (

            {loading && <Spinner />}

            {buttons}

      );
    }

    if (message) {
      return (

          <Dropdown
            width={440}
            ref={(dropdown) => this._dropdown = dropdown}
            offsetY={8}
          >
            <UserAvatar user={this.props.build.createdBy} style={{ width: 32, height: 32 }} className="cursor-pointer" />

                <Icon icon="unknown-user" title="Unknown build email" style={{ width: 32, height: 32 }} />

              {message}

            {buttonContent}
          </Dropdown>

      );
    }

    return (

        <UserAvatar
          user={this.props.build.createdBy}
          style={{ width: 32, height: 32, border: '1px solid #ccc', background: 'white' }}
        />

    );
  }

  handleMutationFailure = (transaction) => {
    this.setState({ isAddingEmail: false, isDismissing: false });

    FlashesStore.flash(FlashesStore.ERROR, transaction.getError());
  };

  handleDismissClick = () => {
    this.setState({ isDismissing: true });

    const mutation = new NoticeDismissMutation({ viewer: this.props.viewer, notice: this.props.viewer.notice });

    Relay.Store.commitUpdate(mutation, { onSuccess: this.handleDismissedSucess, onFailure: this.handleMutationFailure });

    this._dropdown.setShowing(false);
  };

  handleAddEmailClick = () => {
    this.setState({ isAddingEmail: true });

    const mutation = new EmailCreateMutation({ address: this.props.build.createdBy.email, viewer: this.props.viewer });

    Relay.Store.commitUpdate(mutation, { onSuccess: this.handleEmailAddedSuccess, onFailure: this.handleMutationFailure });
  };

  handleDismissedSucess = () => {
    this.setState({ isDismissing: false });
  };

  handleEmailAddedSuccess = () => {
    this.setState({ isAddingEmail: false, hasSentSomething: true });
  };

  handleLocalDismissClick = () => {
    this.setState({ hasBeenDismissed: true });
  }
}

export default Relay.createContainer(AvatarWithUnknownEmailPrompt, {
  initialVariables: {
    emailForPrompt: null,
    isTryingToPrompt: false
  },

  fragments: {
    build: () => Relay.QL`
      fragment on Build {
        createdBy {
          __typename
          ...on UnregisteredUser {
            name
            email
            avatar {
              url
            }
          }
          ...on User {
            name
            email
            avatar {
              url
            }
          }
        }
      }
    `,
    viewer: () => Relay.QL`
      fragment on Viewer {
        ${EmailCreateMutation.getFragment('viewer')}
        emails(first: 50) {
          edges {
            node {
              id
              address
              verified
            }
          }
        }
        notice(namespace: NOTICE_NAMESPACE_EMAIL_SUGGESTION, scope: $emailForPrompt) @include(if: $isTryingToPrompt) {
          ${NoticeDismissMutation.getFragment('notice')}
          dismissedAt
        }
      }
    `
  }
});
<commit_msg>Some random avatar prompt code cleanups<commit_after>import React from 'react';
import Relay from 'react-relay';
import shallowCompare from 'react-addons-shallow-compare';

import Dropdown from '../shared/Dropdown';
import Icon from '../shared/Icon';
import Spinner from '../shared/Spinner';
import UserAvatar from '../shared/UserAvatar';

import FlashesStore from '../../stores/FlashesStore';

import EmailCreateMutation from '../../mutations/EmailCreate';
import NoticeDismissMutation from '../../mutations/NoticeDismiss';

class AvatarWithUnknownEmailPrompt extends React.Component {
  static propTypes = {
    build: React.PropTypes.shape({
      createdBy: React.PropTypes.shape({
        email: React.PropTypes.string,
        name: React.PropTypes.string
      })
    }).isRequired,
    viewer: React.PropTypes.shape({
      emails: React.PropTypes.shape({
        edges: React.PropTypes.arrayOf(
          React.PropTypes.shape({
            node: React.PropTypes.shape({
              id: React.PropTypes.string,
              address: React.PropTypes.string,
              verified: React.PropTypes.bool
            })
          })
        )
      }).isRequired,
      notice: React.PropTypes.shape({
        dismissedAt: React.PropTypes.string
      })
    }).isRequired,
    relay: React.PropTypes.object
  };

  state = {
    isAddingEmail: false,
    isDismissing: false,
    hasSentSomething: false,
    hasBeenDismissed: false
  };

  // Given an email address, try and return the associated UserEmail GraphQL
  // node
  findUserEmailNode(email) {
    const userEmails = this.props.viewer.emails.edges;

    const userEmail = userEmails.find(
      ({ node: { address: userEmail } }) => (
        userEmail.toLowerCase() === email.toLowerCase()
      )
    );

    if (userEmail) {
      return userEmail.node;
    }
  }

  // Given an email address, returns true/false depending on whether or not the
  // current user owns that email and it's been verified
  isCurrentUsersValidatedEmail(email) {
    const userEmail = this.findUserEmailNode(email);

    return userEmail && userEmail.verified;
  }

  // Determines if this paticular build should prompt the current user to add
  // it's associated email to their user account
  isUnregisteredWithEmail(createdBy) {
    return (createdBy && createdBy.email && createdBy.__typename === "UnregisteredUser");
  }

  shouldComponentUpdate(nextProps, nextState) {
    return shallowCompare(this, nextProps, nextState);
  }

  componentDidMount() {
    const createdBy = this.props.build.createdBy;

    // Before showing the prompt, we should first check to see if we even
    // should. The first check `isUnregisteredWithEmail` makes sure that the
    // associated user on this build is not a Buildkite user and has an email
    // we can link to.
    //
    // The second check makes sure that the user hasn't already added and
    // verified the email, but is waiting for Buildkite's backend to link the
    // user to the build.
    if (this.isUnregisteredWithEmail(createdBy) && !this.isCurrentUsersValidatedEmail(createdBy.email)) {
      this.props.relay.setVariables(
        {
          isTryingToPrompt: true,
          emailForPrompt: createdBy.email.toLowerCase()
        },
        (readyState) => {
          if (readyState.done) {
            setTimeout(() => {
              const { dismissedAt } = this.props.viewer.notice;

              if (this._dropdown && !dismissedAt) {
                this._dropdown.setShowing(true);
              }
            }, 0);
          }
        }
      );
    }
  }

  getMessages(loading) {
    // If we haven't decided to send a query for this yet, don't show anything!
    if (!this.props.relay.variables.isTryingToPrompt) {
      return {};
    }

    let message;
    let buttons;

    const userEmail = this.findUserEmailNode(this.props.build.createdBy.email);

    if (!this.state.isAddingEmail && userEmail) {
      if (userEmail.verified) {
        return {};
      }

      message = (

Email verification needed

We’ve sent a verification email to {this.props.build.createdBy.email}. Click the link in that email to finish adding it to your account.

You can resend the verification email or remove this email address in your Personal Email Settings

      );
    } else {
      // Otherwise, we've got an unknown (to Buildkite) email address on our hands!
      message = (

Unknown build commit email

The email {this.props.build.createdBy.email} could not be matched to any users in your organization. If this email address belongs to you, add it to your personal list of email addresses.

      );
      buttons = [
        <button
          key="add-email"
          className="btn btn-primary flex-auto m1"
          onClick={this.handleAddEmailClick}
          disabled={loading}
        >
          Add Email Address
        </button>,
        <button
          key="dismiss-email"
          className="btn btn-outline border-gray flex-auto m1"
          onClick={this.handleDismissClick}
          disabled={loading}
        >
          Not My Email Address
        </button>
      ];
    }

    return { message, buttons, loading };
  }

  isLoading = () => (
    this.state.isAddingEmail || this.state.isDismissing
  );

  render() {
    const loading = this.isLoading();

    const { message, buttons } = this.getMessages(loading);
    let buttonContent;

    if (buttons) {
      buttonContent = (

            {loading && <Spinner />}

            {buttons}

      );
    }

    if (message) {
      return (

          <Dropdown
            width={440}
            ref={(dropdown) => this._dropdown = dropdown}
            offsetY={8}
          >
            <UserAvatar user={this.props.build.createdBy} style={{ width: 32, height: 32 }} className="cursor-pointer" />

                <Icon icon="unknown-user" title="Unknown build email" style={{ width: 32, height: 32 }} />

              {message}

            {buttonContent}
          </Dropdown>

      );
    }

    return (

        <UserAvatar
          user={this.props.build.createdBy}
          style={{ width: 32, height: 32, border: '1px solid #ccc', background: 'white' }}
        />

    );
  }

  handleMutationFailure = (transaction) => {
    this.setState({ isAddingEmail: false, isDismissing: false });

    FlashesStore.flash(FlashesStore.ERROR, transaction.getError());
  };

  handleDismissClick = () => {
    this.setState({ isDismissing: true });

    const mutation = new NoticeDismissMutation({ viewer: this.props.viewer, notice: this.props.viewer.notice });

    Relay.Store.commitUpdate(mutation, { onSuccess: this.handleDismissedSucess, onFailure: this.handleMutationFailure });

    this._dropdown.setShowing(false);
  };

  handleAddEmailClick = () => {
    this.setState({ isAddingEmail: true });

    const mutation = new EmailCreateMutation({ address: this.props.build.createdBy.email, viewer: this.props.viewer });

    Relay.Store.commitUpdate(mutation, { onSuccess: this.handleEmailAddedSuccess, onFailure: this.handleMutationFailure });
  };

  handleDismissedSucess = () => {
    this.setState({ isDismissing: false });
  };

  handleEmailAddedSuccess = () => {
    this.setState({ isAddingEmail: false, hasSentSomething: true });
  };

  handleLocalDismissClick = () => {
    this.setState({ hasBeenDismissed: true });
  }
}

export default Relay.createContainer(AvatarWithUnknownEmailPrompt, {
  initialVariables: {
    emailForPrompt: null,
    isTryingToPrompt: false
  },

  fragments: {
    build: () => Relay.QL`
      fragment on Build {
        createdBy {
          __typename
          ...on UnregisteredUser {
            name
            email
            avatar {
              url
            }
          }
          ...on User {
            name
            email
            avatar {
              url
            }
          }
        }
      }
    `,
    viewer: () => Relay.QL`
      fragment on Viewer {
        ${EmailCreateMutation.getFragment('viewer')}
        emails(first: 50) {
          edges {
            node {
              id
              address
              verified
            }
          }
        }
        notice(namespace: NOTICE_NAMESPACE_EMAIL_SUGGESTION, scope: $emailForPrompt) @include(if: $isTryingToPrompt) {
          ${NoticeDismissMutation.getFragment('notice')}
          dismissedAt
        }
      }
    `
  }
});
<|endoftext|>


<commit_before>var GF = function (GENE_STRUCTURE, options) {

    "use strict";

    // Check for invalid parameter GENE_STRUCTURE
    if ((typeof GENE_STRUCTURE === "undefined")) {
        console.error("Invalid parameter passed to GF. You need to pass a gene structure.");
        return null;
    }

    // Class private methods/properties
    var gfprivate = {};

    // Gene structure
    gfprivate.GENE_STRUCTURE = GENE_STRUCTURE; // Gene structure

    // Maximum population size
    gfprivate.MAX_POPULATION_SIZE = 45; // default to 45

    // Maximum number of generations
    gfprivate.MAX_GENERATIONS = 100; // default to 100

    // Mutation probability
    gfprivate.MUTATION_PROB = 0.05;
    c // default to 5%

    // Selection number
    gfprivate.NUM_TO_SELECT = 10; // default to 10 tributes (they volunteer as tribute)

    // Chromosome atrributes
    gfprivate.chromosome = {
        genes: {},
        score: 0
    };

    // Generation Population Array
    gfprivate.population = [];

    // Tests a single chromosome's fitness and returns the score
    gfprivate.getFitnessScore = function (genes) {
        var score = gfprivate.fitnessFunction(genes);
        return score;
    };

    // Sort (descending) chromosomes based on score
    gfprivate.sortPopulation = function () {

    };

    // Crossover Xgene and Ygene and return crossover
    gfprivate.crossover = function (Xgenes, Ygenes) {
        var crossedGenes = {};
        for (var property in Xgene) {
            if (Xgene.hasOwnProperty(property)) {
                // 50/50 chance of picking a gene from either
                crossedGenes = ((Math.random() > 0.5) ? Xgenes[property] : Ygenes[property]);
            }
        }
    };

    // Mutate individual chromosomes
    gfprivate.getMutated = function (genes) {
        var mutatedGene = {};
        for (var property in genes) {
            if (genes.hasOwnProperty(property)) {
                var mutatedGene[property] = ((Math.random() < gfprivate.MUTATION_PROB) ? Math.random() : genes[property]);
            }
        }
        return mutatedGene;
    };

    // Generates a new random chromosome using the structure provided
    gfprivate.generateChromosome = function () {
        var newChromosome = gfprivate.GENE_STRUCTURE;
        for (var property in newChromosome) {
            if (newChromosome.hasOwnProperty(property)) {
                newChromosome[property] = Math.random();
            }
        }
        return newChromosome;
    };

    // Default fitness function (should be replaced with custom function by framework user)
    gfprivate.fitnessFunction = function (genes) {
        var score = 0;
        // Do stuff using the genes
        // Return score
        return score;
    };

    // Class Public methods/properties
    var public = {};

    public.initPopulation = function (chromosomeStruct) {

        /* CREATE RANDOM POPULATION */

        for (i = 0; i <= gfprivate.MAX_POPULATION_SIZE; i += 1) {
            // Create new random chromosome
            var newChromosome = gfprivate.generateChromosome();
            // Add it to the population
            gfprivate.population.push(newChromosome);
        };
    };

    public.evolve = function (evolveOptions) {

        // Update options again.
        $.extend(gfprivate, evolveOptions);

        // The generation counter
        var generationCount = gfprivate.MAX_GENERATIONS;

        // Set the fitness function
        gfprivate.fitnessFunction = fitnessFunction;

        // Abort
        // If the population is not initialized
        if (gfprivate.population.empty()) {
            console.error("No population initialized. Hint: Use .init before .evolve");
            return;
        };

        while (generationCount--) {

            /* EVALUATION PHASE */

            // Pass all chromosomes to fitness function and get all scores
            for (i = 0; i <= gfprivate.population.length; i += 1) {
                // Get the fitness core
                var score = gfprivate.getFitnessScore(gfprivate.population[i].genes);
                // Set the score property of the chromosome
                gfprivate.population[i].score = score;
            };

            /* SELECTION PHASE */

            // Sort most fit to least fit chromosomes
            gfprivate.sortPopulation();
            // Select best chromosomes
            var fittestChromosomes = [];
            for (k = 0; k < gfprivate.NUM_TO_SELECT; k += 1) {
                fittestChromosomes.push(gfprivate.population[k]);
            }

            /* CROSSOVER & MUTATION PHASE */

            var crossoverPopulation = [];
            for (j = 0; j < fitnessFunction.length; j += 1) {
                for (jj = 1; jj < fitnessFunction.length - j; jj += 1) {
                    // 50/50 chance for selecting genes
                    var crossoverChromosome = gfprivate.crossover(gfprivate.population[j], gfprivate.population[jj]);
                    crossoverPopulation.push(crossoverChromosome);
                }
            }

            /* ASSIGN CROSS OVER AS NEW POPULATION */

            gfprivate.population = crossoverPopulation;

            /* REPEAT UNTIL LAST GENERATION */
        }
    };

    // Options merging (requires Jquery)
    $.extend(gfprivate, options);

    return public;
};
<commit_msg>Fix random code<commit_after>var GF = function (GENE_STRUCTURE, options) {

    "use strict";

    // Check for invalid parameter GENE_STRUCTURE
    if ((typeof GENE_STRUCTURE === "undefined")) {
        console.error("Invalid parameter passed to GF. You need to pass a gene structure.");
        return null;
    }

    // Class private methods/properties
    var gfprivate = {};

    // Gene structure
    gfprivate.GENE_STRUCTURE = GENE_STRUCTURE; // Gene structure

    // Maximum population size
    gfprivate.MAX_POPULATION_SIZE = 45; // default to 45

    // Maximum number of generations
    gfprivate.MAX_GENERATIONS = 100; // default to 100

    // Mutation probability
    gfprivate.MUTATION_PROB = 0.05; // default to 5%

    // Selection number
    gfprivate.NUM_TO_SELECT = 10; // default to 10 tributes (they volunteer as tribute)

    // Chromosome atrributes
    gfprivate.chromosome = {
        genes: {},
        score: 0
    };

    // Generation Population Array
    gfprivate.population = [];

    // Tests a single chromosome's fitness and returns the score
    gfprivate.getFitnessScore = function (genes) {
        var score = gfprivate.fitnessFunction(genes);
        return score;
    };

    // Sort (descending) chromosomes based on score
    gfprivate.sortPopulation = function () {

    };

    // Crossover Xgene and Ygene and return crossover
    gfprivate.crossover = function (Xgenes, Ygenes) {
        var crossedGenes = {};
        for (var property in Xgene) {
            if (Xgene.hasOwnProperty(property)) {
                // 50/50 chance of picking a gene from either
                crossedGenes = ((Math.random() > 0.5) ? Xgenes[property] : Ygenes[property]);
            }
        }
    };

    // Mutate individual chromosomes
    gfprivate.getMutated = function (genes) {
        var mutatedGene = {};
        for (var property in genes) {
            if (genes.hasOwnProperty(property)) {
                var mutatedGene[property] = ((Math.random() < gfprivate.MUTATION_PROB) ? Math.random() : genes[property]);
            }
        }
        return mutatedGene;
    };

    // Generates a new random chromosome using the structure provided
    gfprivate.generateChromosome = function () {
        var newChromosome = gfprivate.GENE_STRUCTURE;
        for (var property in newChromosome) {
            if (newChromosome.hasOwnProperty(property)) {
                newChromosome[property] = Math.random();
            }
        }
        return newChromosome;
    };

    // Default fitness function (should be replaced with custom function by framework user)
    gfprivate.fitnessFunction = function (genes) {
        var score = 0;
        // Do stuff using the genes
        // Return score
        return score;
    };

    // Class Public methods/properties
    var public = {};

    public.initPopulation = function (chromosomeStruct) {

        /* CREATE RANDOM POPULATION */

        for (i = 0; i <= gfprivate.MAX_POPULATION_SIZE; i += 1) {
            // Create new random chromosome
            var newChromosome = gfprivate.generateChromosome();
            // Add it to the population
            gfprivate.population.push(newChromosome);
        };
    };

    public.evolve = function (evolveOptions) {

        // Update options again.
        $.extend(gfprivate, evolveOptions);

        // The generation counter
        var generationCount = gfprivate.MAX_GENERATIONS;

        // Set the fitness function
        gfprivate.fitnessFunction = fitnessFunction;

        // Abort
        // If the population is not initialized
        if (gfprivate.population.empty()) {
            console.error("No population initialized. Hint: Use .init before .evolve");
            return;
        };

        while (generationCount--) {

            /* EVALUATION PHASE */

            // Pass all chromosomes to fitness function and get all scores
            for (i = 0; i <= gfprivate.population.length; i += 1) {
                // Get the fitness core
                var score = gfprivate.getFitnessScore(gfprivate.population[i].genes);
                // Set the score property of the chromosome
                gfprivate.population[i].score = score;
            };

            /* SELECTION PHASE */

            // Sort most fit to least fit chromosomes
            gfprivate.sortPopulation();
            // Select best chromosomes
            var fittestChromosomes = [];
            for (k = 0; k < gfprivate.NUM_TO_SELECT; k += 1) {
                fittestChromosomes.push(gfprivate.population[k]);
            }

            /* CROSSOVER & MUTATION PHASE */

            var crossoverPopulation = [];
            for (j = 0; j < fitnessFunction.length; j += 1) {
                for (jj = 1; jj < fitnessFunction.length - j; jj += 1) {
                    // 50/50 chance for selecting genes
                    var crossoverChromosome = gfprivate.crossover(gfprivate.population[j], gfprivate.population[jj]);
                    crossoverPopulation.push(crossoverChromosome);
                }
            }

            /* ASSIGN CROSS OVER AS NEW POPULATION */

            gfprivate.population = crossoverPopulation;

            /* REPEAT UNTIL LAST GENERATION */
        }
    };

    // Options merging (requires Jquery)
    $.extend(gfprivate, options);

    return public;
};
<|endoftext|>


<commit_before>(function () {

	var notifyArrayDependants,

		reassignDependants,
		sidewaysShift,
		queueReassignments,
		dispatchReassignmentQueue,
		dispatchIndexRefReassignmentQueue,

		wrapArray,
		unwrapArray,
		WrappedArrayProto,
		testObj,
		mutatorMethods;

	// Register a keypath to this array. When any of this array's mutator methods are called,
	// it will `set` that keypath on the given Ractive instance
	registerKeypathToArray = function ( array, keypath, root ) {
		var roots, keypathsByGuid, rootIndex, keypaths;

		// If this array hasn't been wrapped, we need to wrap it
		if ( !array._ractive ) {
			defineProperty( array, '_ractive', {
				value: {
					roots: [ root ], // there may be more than one Ractive instance depending on this
					keypathsByGuid: {}
				},
				configurable: true
			});

			array._ractive.keypathsByGuid[ root._guid ] = [ keypath ];

			wrapArray( array );
		}

		else {
			roots = array._ractive.roots;
			keypathsByGuid = array._ractive.keypathsByGuid;

			// Does this Ractive instance currently depend on this array?
			// If not, associate them
			if ( !keypathsByGuid[ root._guid ] ) {
				roots[ roots.length ] = root;
				keypathsByGuid[ root._guid ] = [];
			}

			keypaths = keypathsByGuid[ root._guid ];

			// If the current keypath isn't among them, add it
			if ( keypaths.indexOf( keypath ) === -1 ) {
				keypaths[ keypaths.length ] = keypath;
			}
		}
	};

	// Unregister keypath from array
	unregisterKeypathFromArray = function ( array, keypath, root ) {
		var roots, keypathsByGuid, rootIndex, keypaths, keypathIndex;

		if ( !array._ractive ) {
			throw new Error( 'Attempted to remove keypath from non-wrapped array. This error is unexpected - please send a bug report to @rich_harris' );
		}

		roots = array._ractive.roots;
		keypathsByGuid = array._ractive.keypathsByGuid;

		if ( !keypathsByGuid[ root._guid ] ) {
			throw new Error( 'Ractive instance was not listed as a dependent of this array. This error is unexpected - please send a bug report to @rich_harris' );
		}

		keypaths = keypathsByGuid[ root._guid ];
		keypathIndex = keypaths.indexOf( keypath );

		if ( keypathIndex === -1 ) {
			throw new Error( 'Attempted to unlink non-linked keypath from array. This error is unexpected - please send a bug report to @rich_harris' );
		}

		keypaths.splice( keypathIndex, 1 );

		if ( !keypaths.length ) {
			roots.splice( roots.indexOf( root ), 1 );
			keypathsByGuid[ root._guid ] = null;
		}

		if ( !roots.length ) {
			unwrapArray( array ); // It's good to clean up after ourselves
		}
	};

	notifyArrayDependants = function ( array, methodName, args ) {
		var processRoots,
			processRoot,
			processKeypaths,
			processKeypath,
			queueAllDependants,
			queueDependants,
			keypathsByGuid;

		keypathsByGuid = array._ractive.keypathsByGuid;

		processRoots = function ( roots ) {
			var i = roots.length;
			while ( i-- ) {
				processRoot( roots[i] );
			}
		};

		processRoot = function ( root ) {
			root._transitionManager = makeTransitionManager( noop );
			processKeypaths( root, keypathsByGuid[ root._guid ] );
			root._transitionManager = null;
		};

		processKeypaths = function ( root, keypaths ) {
			var i = keypaths.length;
			while ( i-- ) {
				processKeypath( root, keypaths[i] );
			}
		};

		processKeypath = function ( root, keypath ) {
			var depsByKeypath, deps, keys, upstreamQueue, smartUpdateQueue, dumbUpdateQueue, i, j, item;

			upstreamQueue = [];

			// We don't do root.set(), because we don't want to update DOM sections
			// using the normal method - we want to do a smart update whereby elements
			// are removed from the right place. But we do need to clear the cache
			clearCache( root, keypath );

			// First, notify direct dependants of upstream keypaths...
			keys = splitKeypath( keypath );
			while ( keys.length ) {
				keys.pop();
				upstreamQueue[ upstreamQueue.length ] = keys.join( '.' );
			}

			// ...and length property!
			upstreamQueue[ upstreamQueue.length ] = keypath + '.length';

			notifyMultipleDependants( root, upstreamQueue, true );

			// we probably need to reassign a whole bunch of dependants
			// (e.g. 'items.4' becomes 'items.3' if we're shifting)
			//reassignDependants( root, keypath, array, methodName, args );

			// find dependants. If any are DOM sections, we do a smart update
			// rather than a ractive.set() blunderbuss
			smartUpdateQueue = [];
			dumbUpdateQueue = [];

			for ( i=0; i<root._deps.length; i+=1 ) { // we can't cache root._deps.length as it may change!
				depsByKeypath = root._deps[i];

				if ( !depsByKeypath ) {
					continue;
				}

				deps = depsByKeypath[ keypath ];

				if ( deps ) {
					queueDependants( root, keypath, deps, smartUpdateQueue, dumbUpdateQueue );

					// we may have some deferred evaluators to process
					processDeferredUpdates( root );

					while ( smartUpdateQueue.length ) {
						smartUpdateQueue.pop().smartUpdate( methodName, args );
					}

					while ( dumbUpdateQueue.length ) {
						dumbUpdateQueue.pop().update();
					}
				}
			}

			// we may have some deferred attributes to process
			processDeferredUpdates( root );
		};

		// TODO can we get rid of this whole queueing nonsense?
		queueDependants = function ( root, keypath, deps, smartUpdateQueue, dumbUpdateQueue ) {
			var k, dependant;

			k = deps.length;
			while ( k-- ) {
				dependant = deps[k];

				// references need to get processed before mustaches
				if ( dependant.type === REFERENCE ) {
					dependant.update();
				}

				// is this a DOM section?
				else if ( dependant.keypath === keypath && dependant.type === SECTION /*&& dependant.parentNode*/ ) {
					smartUpdateQueue[ smartUpdateQueue.length ] = dependant;

				} else {
					dumbUpdateQueue = dependant;
				}
			}
		};

		processRoots( array._ractive.roots );
	};

	WrappedArrayProto = [];
	mutatorMethods = [ 'pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift' ];

	mutatorMethods.forEach( function ( methodName ) {
		var method = function () {
			var result = Array.prototype[ methodName ].apply( this, arguments );

			this._ractive.setting = true;
			notifyArrayDependants( this, methodName, arguments );
			this._ractive.setting = false;

			return result;
		};

		defineProperty( WrappedArrayProto, methodName, {
			value: method
		});
	});

	// can we use prototype chain injection?
	// http://perfectionkills.com/how-ecmascript-5-still-does-not-allow-to-subclass-an-array/#wrappers_prototype_chain_injection
	testObj = {};
	if ( testObj.__proto__ ) {
		// yes, we can
		wrapArray = function ( array ) {
			array.__proto__ = WrappedArrayProto;
		};

		unwrapArray = function ( array ) {
			delete array._ractive;
			array.__proto__ = Array.prototype;
		};
	}

	else {
		// no, we can't
		wrapArray = function ( array ) {
			var i, methodName;

			i = mutatorMethods.length;
			while ( i-- ) {
				methodName = mutatorMethods[i];
				defineProperty( array, methodName, {
					value: WrappedArrayProto[ methodName ]
				});
			}
		};

		unwrapArray = function ( array ) {
			var i;

			i = mutatorMethods.length;
			while ( i-- ) {
				delete array[ mutatorMethods[i] ];
			}

			delete array._ractive;
		};
	}

}());<commit_msg>reverse order of array smart updates, in order to prevent an awkward edge case<commit_after>(function () {

	var notifyArrayDependants,

		reassignDependants,
		sidewaysShift,
		queueReassignments,
		dispatchReassignmentQueue,
		dispatchIndexRefReassignmentQueue,

		wrapArray,
		unwrapArray,
		WrappedArrayProto,
		testObj,
		mutatorMethods;

	// Register a keypath to this array. When any of this array's mutator methods are called,
	// it will `set` that keypath on the given Ractive instance
	registerKeypathToArray = function ( array, keypath, root ) {
		var roots, keypathsByGuid, rootIndex, keypaths;

		// If this array hasn't been wrapped, we need to wrap it
		if ( !array._ractive ) {
			defineProperty( array, '_ractive', {
				value: {
					roots: [ root ], // there may be more than one Ractive instance depending on this
					keypathsByGuid: {}
				},
				configurable: true
			});

			array._ractive.keypathsByGuid[ root._guid ] = [ keypath ];

			wrapArray( array );
		}

		else {
			roots = array._ractive.roots;
			keypathsByGuid = array._ractive.keypathsByGuid;

			// Does this Ractive instance currently depend on this array?
			// If not, associate them
			if ( !keypathsByGuid[ root._guid ] ) {
				roots[ roots.length ] = root;
				keypathsByGuid[ root._guid ] = [];
			}

			keypaths = keypathsByGuid[ root._guid ];

			// If the current keypath isn't among them, add it
			if ( keypaths.indexOf( keypath ) === -1 ) {
				keypaths[ keypaths.length ] = keypath;
			}
		}
	};

	// Unregister keypath from array
	unregisterKeypathFromArray = function ( array, keypath, root ) {
		var roots, keypathsByGuid, rootIndex, keypaths, keypathIndex;

		if ( !array._ractive ) {
			throw new Error( 'Attempted to remove keypath from non-wrapped array. This error is unexpected - please send a bug report to @rich_harris' );
		}

		roots = array._ractive.roots;
		keypathsByGuid = array._ractive.keypathsByGuid;

		if ( !keypathsByGuid[ root._guid ] ) {
			throw new Error( 'Ractive instance was not listed as a dependent of this array. This error is unexpected - please send a bug report to @rich_harris' );
		}

		keypaths = keypathsByGuid[ root._guid ];
		keypathIndex = keypaths.indexOf( keypath );

		if ( keypathIndex === -1 ) {
			throw new Error( 'Attempted to unlink non-linked keypath from array. This error is unexpected - please send a bug report to @rich_harris' );
		}

		keypaths.splice( keypathIndex, 1 );

		if ( !keypaths.length ) {
			roots.splice( roots.indexOf( root ), 1 );
			keypathsByGuid[ root._guid ] = null;
		}

		if ( !roots.length ) {
			unwrapArray( array ); // It's good to clean up after ourselves
		}
	};

	notifyArrayDependants = function ( array, methodName, args ) {
		var processRoots,
			processRoot,
			processKeypaths,
			processKeypath,
			queueAllDependants,
			queueDependants,
			keypathsByGuid;

		keypathsByGuid = array._ractive.keypathsByGuid;

		processRoots = function ( roots ) {
			var i = roots.length;
			while ( i-- ) {
				processRoot( roots[i] );
			}
		};

		processRoot = function ( root ) {
			var previousTransitionManager = root._transitionManager;

			root._transitionManager = makeTransitionManager( noop );
			processKeypaths( root, keypathsByGuid[ root._guid ] );
			root._transitionManager = previousTransitionManager;
		};

		processKeypaths = function ( root, keypaths ) {
			var i = keypaths.length;
			while ( i-- ) {
				processKeypath( root, keypaths[i] );
			}
		};

		processKeypath = function ( root, keypath ) {
			var depsByKeypath, deps, keys, upstreamQueue, smartUpdateQueue, dumbUpdateQueue, i, j, item;

			// We don't do root.set(), because we don't want to update DOM sections
			// using the normal method - we want to do a smart update whereby elements
			// are removed from the right place. But we do need to clear the cache
			clearCache( root, keypath );

			// find dependants. If any are DOM sections, we do a smart update
			// rather than a ractive.set() blunderbuss
			smartUpdateQueue = [];
			dumbUpdateQueue = [];

			for ( i=0; i<root._deps.length; i+=1 ) { // we can't cache root._deps.length as it may change!
				depsByKeypath = root._deps[i];

				if ( !depsByKeypath ) {
					continue;
				}

				deps = depsByKeypath[ keypath ];

				if ( deps ) {
					queueDependants( root, keypath, deps, smartUpdateQueue, dumbUpdateQueue );

					// we may have some deferred evaluators to process
					processDeferredUpdates( root );

					while ( smartUpdateQueue.length ) {
						smartUpdateQueue.pop().smartUpdate( methodName, args );
					}

					while ( dumbUpdateQueue.length ) {
						dumbUpdateQueue.pop().update();
					}
				}
			}

			// we may have some deferred attributes to process
			processDeferredUpdates( root );

			// Finally, notify direct dependants of upstream keypaths...
			upstreamQueue = [];

			keys = splitKeypath( keypath );
			while ( keys.length ) {
				keys.pop();
				upstreamQueue[ upstreamQueue.length ] = keys.join( '.' );
			}

			// ...and length property!
			upstreamQueue[ upstreamQueue.length ] = keypath + '.length';

			notifyMultipleDependants( root, upstreamQueue, true );
		};

		// TODO can we get rid of this whole queueing nonsense?
		queueDependants = function ( root, keypath, deps, smartUpdateQueue, dumbUpdateQueue ) {
			var k, dependant;

			k = deps.length;
			while ( k-- ) {
				dependant = deps[k];

				// references need to get processed before mustaches
				if ( dependant.type === REFERENCE ) {
					dependant.update();
					//dumbUpdateQueue[ dumbUpdateQueue.length ] = dependant;
				}

				// is this a DOM section?
				else if ( dependant.keypath === keypath && dependant.type === SECTION /*&& dependant.parentNode*/ ) {
					smartUpdateQueue[ smartUpdateQueue.length ] = dependant;

				} else {
					dumbUpdateQueue[ dumbUpdateQueue.length ] = dependant;
				}
			}
		};

		processRoots( array._ractive.roots );
	};

	WrappedArrayProto = [];
	mutatorMethods = [ 'pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift' ];

	mutatorMethods.forEach( function ( methodName ) {
		var method = function () {
			var result = Array.prototype[ methodName ].apply( this, arguments );

			this._ractive.setting = true;
			notifyArrayDependants( this, methodName, arguments );
			this._ractive.setting = false;

			return result;
		};

		defineProperty( WrappedArrayProto, methodName, {
			value: method
		});
	});

	// can we use prototype chain injection?
	// http://perfectionkills.com/how-ecmascript-5-still-does-not-allow-to-subclass-an-array/#wrappers_prototype_chain_injection
	testObj = {};
	if ( testObj.__proto__ ) {
		// yes, we can
		wrapArray = function ( array ) {
			array.__proto__ = WrappedArrayProto;
		};

		unwrapArray = function ( array ) {
			delete array._ractive;
			array.__proto__ = Array.prototype;
		};
	}

	else {
		// no, we can't
		wrapArray = function ( array ) {
			var i, methodName;

			i = mutatorMethods.length;
			while ( i-- ) {
				methodName = mutatorMethods[i];
				defineProperty( array, methodName, {
					value: WrappedArrayProto[ methodName ]
				});
			}
		};

		unwrapArray = function ( array ) {
			var i;

			i = mutatorMethods.length;
			while ( i-- ) {
				delete array[ mutatorMethods[i] ];
			}

			delete array._ractive;
		};
	}

}());<|endoftext|>


<commit_before>const CSV = { delimiter: ',' };
const chance = require('chance').Chance();

// Taken from http://www.convertcsv.com/generate-test-data.htm
module.exports = function (columns, rows) {
    var j;
    var cols = columns.length;
    var s = "";
    var fld = "";
    var n = 0;
    var t = "";

    hdr = [];

    rows = rows * 1 || 100;
    for (k = 0; k < cols; k++) { // for each field, determine heading and options
        fld = columns[k].split(/[()]/);
        if (fld.length > 1) n = fld[1];
        hdr.push({
            seqObj: null,
            keyword: columns[k].split('(')[0].toLowerCase().trim(),
            arg: (fld.length > 1) ? fld[1] : ""
        });
        if (hdr[hdr.length - 1].keyword == "seq") {
            if (fld.length > 1 && !isNaN(hdr[hdr.length - 1].arg)) {
                //alert('seq set at '+(hdr.length-1)+", and starting n at "+ hdr[hdr.length-1].arg);
                hdr[hdr.length - 1].seqObj = new SeqObj(hdr[hdr.length - 1].arg);
            } else {
                hdr[hdr.length - 1].seqObj = new SeqObj();
                //alert('seq set at '+hdr.length-1);
            }
            //seqobj.n=hdr[k].arg-1;
        }
    }
    for (j = 1; j <= rows; j++) { // for each row
        for (k = 0; k < cols; k++) { // for each field, generate data cell
            switch (hdr[k].keyword) {
                case 'age':
                    s += chance.age();
                    break;
                case 'alpha': //alpha or alpha(n)
                    n = hdr[k].arg;
                    if (n != "") {
                        s += chance.string({
                            pool: 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ',
                            length: n
                        });
                    } else {
                        s += chance.string({
                            pool: 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
                        });
                    }
                    break;
                case 'birthday':
                    n = hdr[k].arg;
                    switch (n) {
                        case '2':
                            s += chance.birthday({
                                string: true,
                                american: false
                            }).toCsv(CSV.delimiter);
                            break;
                        default:
                            s += chance.birthday({
                                string: true
                            }).toCsv(CSV.delimiter);
                            break;
                    }
                    break;
                case 'bool':
                    s += chance.bool();
                    break;
                case 'city':
                    s += chance.city().toCsv(CSV.delimiter);
                    break;
                case 'ccnumber':
                    s += chance.cc().toCsv(CSV.delimiter);
                    break;
                case 'char': // one character
                    s += chance.character();
                    break;
                case 'date':
                    n = hdr[k].arg;

                    function getRandomInt(min, max) {
                        return Math.floor(Math.random() * (max - min + 1)) + min;
                    }

                    // MySQL Timestamp-compatible year value
                    var year = getRandomInt(1971, 2037);

                    switch (n) {
                        case '2':
                            s += chance.date({
                                string: true,
                                american: false,
                                year: year
                            }).toCsv(CSV.delimiter);
                            break;
                        case '3':
                            t = chance.date({
                                year: year
                            });
                            t = t.getFullYear() + '/' + ("0" + (t.getMonth() + 1)).slice(-2) + '/' + ("0" + t.getDate()).slice(-2);
                            s += t;
                            break;
                        case '4':
                            t = chance.date({
                                year: year
                            });
                            t = t.getFullYear() + ("0" + (t.getMonth() + 1)).slice(-2) + ("0" + t.getDate()).slice(-2);
                            s += t;
                            break;
                        default:
                            s += chance.date({
                                string: true,
                                year: year
                            }).toCsv(CSV.delimiter);
                            break;
                    }
                    break;

                case 'digit':
                    n = hdr[k].arg;
                    if (n != "" && n > 0) {
                        s += chance.string({
                            pool: '0123456789',
                            length: n
                        });
                    } else {
                        s += chance.string({
                            pool: '0123456789'
                        });
                    }
                    break;
                case 'dollar':
                    s += chance.dollar().toCsv(CSV.delimiter);
                    break;
                case 'domain':
                    s += chance.domain().toCsv(CSV.delimiter);
                    break;
                case 'email':
                    s += chance.email().toCsv(CSV.delimiter);
                    break;
                case 'first':
                    s += chance.first().toCsv(CSV.delimiter);
                    break;
                case 'float':
                    s += chance.floating({min: 100, max: 100000, fixed: 2});
                    break;
                case 'gender':
                    s += chance.gender();
                    break;
                case 'guid':
                    s += chance.guid();
                    break;
                case 'integer':
                    s += chance.integer({
                        min: -999999,
                        max: 999999
                    });
                    break;
                case 'last':
                    s += chance.last().toCsv(CSV.delimiter);
                    break;

                case 'latitude':
                    s += chance.latitude();
                    break;
                case 'longitude':
                    s += chance.longitude();
                    break;

                case 'mi': // middle initial
                    s += chance.character({
                        alpha: true,
                        casing: 'upper'
                    });
                    break;
                case 'name':
                    s += chance.name().toCsv(CSV.delimiter);
                    break;
                case 'natural':
                    n = hdr[k].arg;
                    if (n != "" && (n * 1) >= 0) {
                        s += chance.natural({
                            max: n
                        });
                    } else {
                        s += chance.natural();
                    }
                    break;
                case 'paragraph': // argument is max length in characters
                    n = hdr[k].arg;
                    if (n != "" && (n * 1) > 0) {
                        s += chance.paragraph().left(n).toCsv(CSV.delimiter);
                    } else {
                        s += chance.paragraph().toCsv(CSV.delimiter);
                    }
                    break;
                case 'phone':
                    s += chance.phone().toCsv(CSV.delimiter);
                    break;
                case 'pick':
                    fld = hdr[k].arg;
                    s += chance.pick(fld.split('|')).toCsv(CSV.delimiter);
                    break;
                case 'postal':
                    s += chance.postal().toCsv(CSV.delimiter);
                    break;
                case 'province':
                    s += chance.province().toCsv(CSV.delimiter);
                    break;
                case 'sentence': // argument is max length in characters
                    n = hdr[k].arg;
                    if (n != "" && (n * 1) > 0) {
                        s += chance.sentence().left(n).toCsv(CSV.delimiter);
                    } else {
                        s += chance.sentence().toCsv(CSV.delimiter);
                    }
                    break;
                case 'seq':
                    s += hdr[k].seqObj.next();
                    //s += seqobj.next();
                    break;
                case 'state':
                    s += chance.state().toCsv(CSV.delimiter);
                    break;
                case 'street':
                    s += chance.street().toCsv(CSV.delimiter);
                    break;
                case 'string':
                    n = hdr[k].arg;
                    if (n != "" && (n * 1) > 0) {
                        s += chance.string({
                            length: n
                        }).toCsv(CSV.delimiter);
                    } else {
                        s += chance.string().toCsv(CSV.delimiter);
                    }
                    break;
                case 'timestamp':
                    s += chance.timestamp();
                    break;
                case 'word':
                    s += chance.word().toCsv(CSV.delimiter);
                    break;
                case 'yn':
                    s += chance.character({
                        pool: 'YN'
                    });
                    break;
                case 'zip':
                    s += chance.zip();
                    break;
                case 'zip9':
                    s += chance.zip({
                        plusfour: true
                    });
                    break;
                case 'yn':
                    s += chance.character({
                        pool: 'YN'
                    });
                    break;
                default:
                    fld = columns[k];
                    if (fld == "FIELD" + (k + 1)) fld = "";
                    s += fld.toCsv(CSV.delimiter);
                    break;

            }
            if (k < cols - 1) {
                s += CSV.delimiter;
            }
        }
        s += "\n";
    }
    return s;
}
<commit_msg>update<commit_after>const CSV = { delimiter: ',' };
const chance = require('chance').Chance();

// Taken from http://www.convertcsv.com/generate-test-data.htm
module.exports = function (columns, rows) {
    var j;
    var cols = columns.length;
    var s = "";
    var fld = "";
    var n = 0;
    var t = "";

    hdr = [];

    rows = rows * 1 || 100;
    for (k = 0; k < cols; k++) { // for each field, determine heading and options
        fld = columns[k].split(/[()]/);
        if (fld.length > 1) n = fld[1];
        hdr.push({
            seqObj: null,
            keyword: columns[k].split('(')[0].toLowerCase().trim(),
            arg: (fld.length > 1) ? fld[1] : ""
        });
        if (hdr[hdr.length - 1].keyword == "seq") {
            if (fld.length > 1 && !isNaN(hdr[hdr.length - 1].arg)) {
                //alert('seq set at '+(hdr.length-1)+", and starting n at "+ hdr[hdr.length-1].arg);
                hdr[hdr.length - 1].seqObj = new SeqObj(hdr[hdr.length - 1].arg);
            } else {
                hdr[hdr.length - 1].seqObj = new SeqObj();
                //alert('seq set at '+hdr.length-1);
            }
            //seqobj.n=hdr[k].arg-1;
        }
    }
    for (j = 1; j <= rows; j++) { // for each row
        for (k = 0; k < cols; k++) { // for each field, generate data cell
            switch (hdr[k].keyword) {
                case 'age':
                    s += chance.age();
                    break;
                case 'alpha': //alpha or alpha(n)
                    n = hdr[k].arg;
                    if (n != "") {
                        s += chance.string({
                            pool: 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ',
                            length: n
                        });
                    } else {
                        s += chance.string({
                            pool: 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
                        });
                    }
                    break;
                case 'birthday':
                    n = hdr[k].arg;
                    switch (n) {
                        case '2':
                            s += chance.birthday({
                                string: true,
                                american: false
                            }).toCsv(CSV.delimiter);
                            break;
                        default:
                            s += chance.birthday({
                                string: true
                            }).toCsv(CSV.delimiter);
                            break;
                    }
                    break;
                case 'bool':
                    s += chance.bool();
                    break;
                case 'city':
                    s += chance.city().toCsv(CSV.delimiter);
                    break;
                case 'ccnumber':
                    s += chance.cc().toCsv(CSV.delimiter);
                    break;
                case 'char': // one character
                    s += chance.character();
                    break;
                case 'date':
                    n = hdr[k].arg;

                    function getRandomInt(min, max) {
                        return Math.floor(Math.random() * (max - min + 1)) + min;
                    }

                    // MySQL Timestamp-compatible year value
                    var year = getRandomInt(1971, 2037);

                    switch (n) {
                        case '2':
                            s += chance.date({
                                string: true,
                                american: false,
                                year: year
                            }).toCsv(CSV.delimiter);
                            break;
                        case '3':
                            t = chance.date({
                                year: year
                            });
                            t = t.getFullYear() + '/' + ("0" + (t.getMonth() + 1)).slice(-2) + '/' + ("0" + t.getDate()).slice(-2);
                            s += t;
                            break;
                        case '4':
                            t = chance.date({
                                year: year
                            });
                            t = t.getFullYear() + ("0" + (t.getMonth() + 1)).slice(-2) + ("0" + t.getDate()).slice(-2);
                            s += t;
                            break;
                        default:
                            s += chance.date({
                                string: true,
                                year: year
                            }).toCsv(CSV.delimiter);
                            break;
                    }
                    break;

                case 'digit':
                    n = hdr[k].arg;
                    if (n != "" && n > 0) {
                        s += chance.string({
                            pool: '0123456789',
                            length: n
                        });
                    } else {
                        s += chance.string({
                            pool: '0123456789'
                        });
                    }
                    break;
                case 'dollar':
                    s += chance.dollar().toCsv(CSV.delimiter);
                    break;
                case 'domain':
                    s += chance.domain().toCsv(CSV.delimiter);
                    break;
                case 'email':
                    s += chance.email().toCsv(CSV.delimiter);
                    break;
                case 'first':
                    s += chance.first().toCsv(CSV.delimiter);
                    break;
                case 'float':
                    s += chance.floating();
                    break;
                case 'gender':
                    s += chance.gender();
                    break;
                case 'guid':
                    s += chance.guid();
                    break;
                case 'integer':
                    s += chance.integer({
                        min: -999999,
                        max: 999999
                    });
                    break;
                case 'last':
                    s += chance.last().toCsv(CSV.delimiter);
                    break;

                case 'latitude':
                    s += chance.latitude();
                    break;
                case 'longitude':
                    s += chance.longitude();
                    break;

                case 'mi': // middle initial
                    s += chance.character({
                        alpha: true,
                        casing: 'upper'
                    });
                    break;
                case 'name':
                    s += chance.name().toCsv(CSV.delimiter);
                    break;
                case 'natural':
                    n = hdr[k].arg;
                    if (n != "" && (n * 1) >= 0) {
                        s += chance.natural({
                            max: n
                        });
                    } else {
                        s += chance.natural();
                    }
                    break;
                case 'paragraph': // argument is max length in characters
                    n = hdr[k].arg;
                    if (n != "" && (n * 1) > 0) {
                        s += chance.paragraph().left(n).toCsv(CSV.delimiter);
                    } else {
                        s += chance.paragraph().toCsv(CSV.delimiter);
                    }
                    break;
                case 'phone':
                    s += chance.phone().toCsv(CSV.delimiter);
                    break;
                case 'pick':
                    fld = hdr[k].arg;
                    s += chance.pick(fld.split('|')).toCsv(CSV.delimiter);
                    break;
                case 'postal':
                    s += chance.postal().toCsv(CSV.delimiter);
                    break;
                case 'province':
                    s += chance.province().toCsv(CSV.delimiter);
                    break;
                case 'sentence': // argument is max length in characters
                    n = hdr[k].arg;
                    if (n != "" && (n * 1) > 0) {
                        s += chance.sentence().left(n).toCsv(CSV.delimiter);
                    } else {
                        s += chance.sentence().toCsv(CSV.delimiter);
                    }
                    break;
                case 'seq':
                    s += hdr[k].seqObj.next();
                    //s += seqobj.next();
                    break;
                case 'state':
                    s += chance.state().toCsv(CSV.delimiter);
                    break;
                case 'street':
                    s += chance.street().toCsv(CSV.delimiter);
                    break;
                case 'string':
                    n = hdr[k].arg;
                    if (n != "" && (n * 1) > 0) {
                        s += chance.string({
                            length: n
                        }).toCsv(CSV.delimiter);
                    } else {
                        s += chance.string().toCsv(CSV.delimiter);
                    }
                    break;
                case 'timestamp':
                    s += chance.timestamp();
                    break;
                case 'word':
                    s += chance.word().toCsv(CSV.delimiter);
                    break;
                case 'yn':
                    s += chance.character({
                        pool: 'YN'
                    });
                    break;
                case 'zip':
                    s += chance.zip();
                    break;
                case 'zip9':
                    s += chance.zip({
                        plusfour: true
                    });
                    break;
                case 'yn':
                    s += chance.character({
                        pool: 'YN'
                    });
                    break;
                default:
                    fld = columns[k];
                    if (fld == "FIELD" + (k + 1)) fld = "";
                    s += fld.toCsv(CSV.delimiter);
                    break;

            }
            if (k < cols - 1) {
                s += CSV.delimiter;
            }
        }
        s += "\n";
    }
    return s;
}
<|endoftext|>


<commit_before>'use strict';

var _ = require('lodash');
var moment = require('moment');
var times = require('../times');
var levels = require('../levels');
var timeago = require('./timeago')();

function init() {

  var openaps = {
    name: 'openaps'
    , label: 'OpenAPS'
    , pluginType: 'pill-status'
  };

  openaps.getPrefs = function getPrefs(sbx) {
    return {
      warn: sbx.extendedSettings.warn ? sbx.extendedSettings.warn : 30,
      urgent: sbx.extendedSettings.urgent ? sbx.extendedSettings.urgent : 60,
      enableAlerts: sbx.extendedSettings.enableAlerts
    };
  };

  openaps.setProperties = function setProperties (sbx) {
    sbx.offerProperty('openaps', function setOpenAPS ( ) {
      return openaps.analyzeData(sbx);
    });
  };

  openaps.analyzeData = function analyzeData (sbx) {
    var recentHours = 6; //TODO dia*2
    var recentMills = sbx.time - times.hours(recentHours).msecs;

    var recentData = _.filter(sbx.data.devicestatus, function (status) {
      return ('openaps' in status) && sbx.entryMills(status) <= sbx.time && sbx.entryMills(status) >= recentMills;
    });

    var prefs = openaps.getPrefs(sbx);
    var recent = moment(sbx.time).subtract(prefs.warn, 'minutes');

    var result = {
      seenDevices: {}
      , lastEnacted: null
      , lastNotEnacted: null
      , lastSuggested: null
      , lastIOB: null
    };

    function getDevice(status) {
      var uri = status.device || 'device';
      var device = result.seenDevices[uri];

      if (!device) {
        device = {
          name: uri.indexOf('openaps://') === 0 ? uri.substring('openaps://'.length) : uri
          , uri: uri
        };

        result.seenDevices[uri] = device;
      }
      return device;
    }

    function toMoments (status) {
      return {
        when:  moment(status.mills)
        , enacted: status.openaps.enacted && status.openaps.enacted.recieved && moment(status.openaps.enacted.timestamp)
        , notEnacted: status.openaps.enacted && !status.openaps.enacted.recieved && moment(status.openaps.enacted.timestamp)
        , suggested: status.openaps.suggested && moment(status.openaps.suggested.timestamp)
        , iob: status.openaps.iob && moment(status.openaps.iob.timestamp)
      };
    }

    function momentsToLoopStatus (moments, noWarning, recent)  {

      var status = {
        symbol: '⚠'
        , code: 'warning'
        , label: 'Warning'
      };

      recent = recent || moments.when.clone().subtract(15, 'minutes');

      if (moments.notEnacted && moments.notEnacted.isAfter(recent)) {
        status.symbol = 'x';
        status.code = 'notenacted';
        status.label = 'Not Enacted';
      } else if (moments.enacted && moments.enacted.isAfter(recent)) {
        status.symbol = '⌁';
        status.code = 'enacted';
        status.label = 'Enacted';
      } else if (moments.suggested && moments.suggested.isAfter(recent)) {
        status.symbol = '↻';
        status.code = 'looping';
        status.label = 'Looping';
      } else if (moments.when && (noWarning || moments.when.isAfter(recent))) {
        status.symbol = '◉';
        status.code = 'waiting';
        status.label = 'Waiting';
      }

      return status;
    }

    _.forEach(recentData, function eachStatus (status) {
      var device = getDevice(status);

      var moments = toMoments(status);
      var loopStatus = momentsToLoopStatus(moments, true);

      if (!device.status || moments.when.isAfter(device.status.when)) {
        device.status = loopStatus;
        device.status.when = moments.when;
      }

      var enacted = status.openaps && status.openaps.enacted;
      if (enacted && moments.enacted && (!result.lastEnacted || moments.enacted.isAfter(result.lastEnacted.moment))) {
        enacted.moment = moment(enacted.timestamp);
        result.lastEnacted = enacted;
      }

      if (enacted && moments.notEnacted && (!result.lastNotEnacted || moments.notEnacted.isAfter(result.lastNotEnacted.moment))) {
        enacted.moment = moment(enacted.timestamp);
        result.lastNotEnacted = enacted;
      }

      var suggested = status.openaps && status.openaps.suggested;
      if (suggested && moments.suggested && (!result.lastSuggested || moments.suggested.isAfter(result.lastSuggested.moment))) {
        suggested.moment = moment(suggested.timestamp);
        result.lastSuggested = suggested;
      }

      var iob = status.openaps && status.openaps.iob;
      if (moments.iob && (!result.lastIOB || moments.iob.isAfter(result.lastIOB.moment))) {
        iob.moment = moments.iob;
        result.lastIOB = iob;
      }
    });

    result.lastLoopMoment = result.lastEnacted && result.lastSuggested ? moment.max(result.lastEnacted.moment, result.lastSuggested.moment) : result.lastEnacted && result.lastEnacted.moment || result.lastSuggested && result.lastSuggested.moment;

    result.status = momentsToLoopStatus({
      enacted: result.lastEnacted && result.lastEnacted.moment
      , notEnacted: result.lastNotEnacted && result.lastNotEnacted.moment
      , suggested: result.lastSuggested && result.lastSuggested.moment
    }, false, recent);

    return result;
  };

  openaps.getEventTypes = function getEventTypes ( ) {
    return [
      {
        val: 'OpenAPS Offline'
        , name: 'OpenAPS Offline'
        , bg: false, insulin: false, carbs: false, prebolus: false, duration: true, percent: false, absolute: false, profile: false, split: false
      }
    ];
  };

  openaps.checkNotifications = function checkNotifications(sbx) {
    var prefs = openaps.getPrefs(sbx);

    if (!prefs.enableAlerts) { return; }

    var prop = sbx.properties.openaps;

    if (!prop.lastLoopMoment) {
      console.info('OpenAPS hasn\'t reported a loop yet');
      return;
    }

    var now = moment();
    var level = statusLevel(prop, prefs, sbx);
    if (level >= levels.WARN) {
      sbx.notifications.requestNotify({
        level: level
        , title: 'OpenAPS isn\'t looping'
        , message: 'Last Loop: ' + formatAgo(prop.lastLoopMoment, now.valueOf())
        , pushoverSound: 'echo'
        , plugin: openaps
        , debug: prop
      });
    }
  };

  openaps.findOfflineMarker = function findOfflineMarker(sbx) {
    return _.findLast(sbx.data.treatments, function match(treatment) {
      var eventTime = sbx.entryMills(treatment);
      var eventEnd = treatment.duration ? eventTime + times.mins(treatment.duration).msecs : eventTime;
      return eventTime <= sbx.time && treatment.eventType === 'OpenAPS Offline' && eventEnd >= sbx.time;
    });
  };

  openaps.updateVisualisation = function updateVisualisation (sbx) {
    var prop = sbx.properties.openaps;

    var prefs = openaps.getPrefs(sbx);

    function valueString (prefix, value) {
      return value ? prefix + value : '';
    }

    var events = [ ];

    function addSuggestion() {
      if (prop.lastSuggested) {

        var valueParts = [
          valueString('BG: ', prop.lastSuggested.bg)
          , valueString(', ', prop.lastSuggested.reason)
          , prop.lastSuggested.mealAssist ? ' Meal Assist: ' + prop.lastSuggested.mealAssist : ''
        ];

        valueParts = concatIOB(valueParts);

        events.push({
          time: prop.lastSuggested.moment
          , value: valueParts.join('')
        });
      }
    }

    function concatIOB (valueParts) {
      if (prop.lastIOB) {
        var bolussnooze = prop.lastIOB.bolussnooze || prop.lastIOB.bolusiob;
        valueParts = valueParts.concat([
          ' IOB: '
          , sbx.roundInsulinForDisplayFormat(prop.lastIOB.iob) + 'U'
          , prop.lastIOB.basaliob ? ', Basal IOB ' + sbx.roundInsulinForDisplayFormat(prop.lastIOB.basaliob) + 'U' : ''
          , bolussnooze ? ', Bolus Snooze ' + sbx.roundInsulinForDisplayFormat(bolussnooze) + 'U' : ''
        ]);
      }

      return valueParts;
    }

    if ('enacted' === prop.status.code) {
      var canceled = prop.lastEnacted.rate === 0 && prop.lastEnacted.duration === 0;

      var valueParts = [
        valueString('BG: ', prop.lastEnacted.bg)
        , ', Temp Basal' + (canceled ? ' Canceled' : ' Started') + ''
        , canceled ? '' : ' ' + prop.lastEnacted.rate.toFixed(2) + ' for ' + prop.lastEnacted.duration + 'm'
        , valueString(', ', prop.lastEnacted.reason)
        , prop.lastEnacted.mealAssist ? ' Meal Assist: ' + prop.lastEnacted.mealAssist : ''
      ];

      if (prop.lastSuggested && prop.lastSuggested.moment.isAfter(prop.lastEnacted.moment)) {
        addSuggestion();
      } else {
        valueParts = concatIOB(valueParts);
      }

      events.push({
        time: prop.lastEnacted.moment
        , value: valueParts.join('')
      });
    } else {
      addSuggestion();
    }

    _.forIn(prop.seenDevices, function seenDevice (device) {
      events.push({
        time: device.status.when
        , value: ['update from', device.name, device.status.symbol, device.status.label].join(' ')
      });
    });

    var sorted = _.sortBy(events, function toMill(event) {
      return event.time.valueOf();
    }).reverse();

    var info = _.map(sorted, function eventToInfo (event) {
      return {
        label: timeAt(false, sbx) + timeFormat(event.time, sbx)
        , value: event.value
      };
    });

    sbx.pluginBase.updatePillText(openaps, {
      value: timeFormat(prop.lastLoopMoment, sbx)
      , label: 'OpenAPS ' + prop.status.symbol
      , info: info
      , pillClass: statusClass(prop, prefs, sbx)
    });
  };

  function statusClass (prop, prefs, sbx) {
    var level = statusLevel(prop, prefs, sbx);
    var cls = 'current';

    if (level === levels.WARN) {
      cls = 'warn';
    } else if (level === levels.URGENT) {
      cls = 'urgent';
    }

    return cls;
  }

  function statusLevel (prop, prefs, sbx) {
    var level = levels.NONE;
    var now = moment(sbx.time);

    if (openaps.findOfflineMarker(sbx)) {
      console.info('OpenAPS known offline, not checking for alerts');
    } else if (prop.lastLoopMoment) {
      var urgentTime = prop.lastLoopMoment.clone().add(prefs.urgent, 'minutes');
      var warningTime = prop.lastLoopMoment.clone().add(prefs.warn, 'minutes');

      if (urgentTime.isBefore(now)) {
        level = levels.URGENT;
      } else if (warningTime.isBefore(now)) {
        level = levels.WARN;
      }
    }

    return level;
  }

  return openaps;

}

function timeFormat (m, sbx) {

  var when;
  if (m && sbx.data.inRetroMode) {
    when = m.format('LT');
  } else if (m) {
    when = formatAgo(m, sbx.time);
  } else {
    when = 'unknown';
  }

  return when;
}

function formatAgo (m, nowMills) {
  var ago = timeago.calcDisplay({mills: m.valueOf()}, nowMills);
  return (ago.value ? ago.value : '') + ago.shortLabel + (ago.shortLabel.length === 1 ? ' ago' : '');
}

function timeAt (prefix, sbx) {
  return sbx.data.inRetroMode ? (prefix ? ' ' : '') + '@ ' : (prefix ? ', ' : '');
}

module.exports = init;<commit_msg>use the same recent date for all comparisons<commit_after>'use strict';

var _ = require('lodash');
var moment = require('moment');
var times = require('../times');
var levels = require('../levels');
var timeago = require('./timeago')();

function init() {

  var openaps = {
    name: 'openaps'
    , label: 'OpenAPS'
    , pluginType: 'pill-status'
  };

  openaps.getPrefs = function getPrefs(sbx) {
    return {
      warn: sbx.extendedSettings.warn ? sbx.extendedSettings.warn : 30,
      urgent: sbx.extendedSettings.urgent ? sbx.extendedSettings.urgent : 60,
      enableAlerts: sbx.extendedSettings.enableAlerts
    };
  };

  openaps.setProperties = function setProperties (sbx) {
    sbx.offerProperty('openaps', function setOpenAPS ( ) {
      return openaps.analyzeData(sbx);
    });
  };

  openaps.analyzeData = function analyzeData (sbx) {
    var recentHours = 6; //TODO dia*2
    var recentMills = sbx.time - times.hours(recentHours).msecs;

    var recentData = _.filter(sbx.data.devicestatus, function (status) {
      return ('openaps' in status) && sbx.entryMills(status) <= sbx.time && sbx.entryMills(status) >= recentMills;
    });

    var prefs = openaps.getPrefs(sbx);
    var recent = moment(sbx.time).subtract(prefs.warn, 'minutes');

    var result = {
      seenDevices: {}
      , lastEnacted: null
      , lastNotEnacted: null
      , lastSuggested: null
      , lastIOB: null
    };

    function getDevice(status) {
      var uri = status.device || 'device';
      var device = result.seenDevices[uri];

      if (!device) {
        device = {
          name: uri.indexOf('openaps://') === 0 ? uri.substring('openaps://'.length) : uri
          , uri: uri
        };

        result.seenDevices[uri] = device;
      }
      return device;
    }

    function toMoments (status) {
      return {
        when:  moment(status.mills)
        , enacted: status.openaps.enacted && status.openaps.enacted.recieved && moment(status.openaps.enacted.timestamp)
        , notEnacted: status.openaps.enacted && !status.openaps.enacted.recieved && moment(status.openaps.enacted.timestamp)
        , suggested: status.openaps.suggested && moment(status.openaps.suggested.timestamp)
        , iob: status.openaps.iob && moment(status.openaps.iob.timestamp)
      };
    }

    function momentsToLoopStatus (moments, noWarning)  {

      var status = {
        symbol: '⚠'
        , code: 'warning'
        , label: 'Warning'
      };

      if (moments.notEnacted && moments.notEnacted.isAfter(recent)) {
        status.symbol = 'x';
        status.code = 'notenacted';
        status.label = 'Not Enacted';
      } else if (moments.enacted && moments.enacted.isAfter(recent)) {
        status.symbol = '⌁';
        status.code = 'enacted';
        status.label = 'Enacted';
      } else if (moments.suggested && moments.suggested.isAfter(recent)) {
        status.symbol = '↻';
        status.code = 'looping';
        status.label = 'Looping';
      } else if (moments.when && (noWarning || moments.when.isAfter(recent))) {
        status.symbol = '◉';
        status.code = 'waiting';
        status.label = 'Waiting';
      }

      return status;
    }

    _.forEach(recentData, function eachStatus (status) {
      var device = getDevice(status);

      var moments = toMoments(status);
      var loopStatus = momentsToLoopStatus(moments, true);

      if (!device.status || moments.when.isAfter(device.status.when)) {
        device.status = loopStatus;
        device.status.when = moments.when;
      }

      var enacted = status.openaps && status.openaps.enacted;
      if (enacted && moments.enacted && (!result.lastEnacted || moments.enacted.isAfter(result.lastEnacted.moment))) {
        enacted.moment = moment(enacted.timestamp);
        result.lastEnacted = enacted;
      }

      if (enacted && moments.notEnacted && (!result.lastNotEnacted || moments.notEnacted.isAfter(result.lastNotEnacted.moment))) {
        enacted.moment = moment(enacted.timestamp);
        result.lastNotEnacted = enacted;
      }

      var suggested = status.openaps && status.openaps.suggested;
      if (suggested && moments.suggested && (!result.lastSuggested || moments.suggested.isAfter(result.lastSuggested.moment))) {
        suggested.moment = moment(suggested.timestamp);
        result.lastSuggested = suggested;
      }

      var iob = status.openaps && status.openaps.iob;
      if (moments.iob && (!result.lastIOB || moments.iob.isAfter(result.lastIOB.moment))) {
        iob.moment = moments.iob;
        result.lastIOB = iob;
      }
    });

    result.lastLoopMoment = result.lastEnacted && result.lastSuggested ? moment.max(result.lastEnacted.moment, result.lastSuggested.moment) : result.lastEnacted && result.lastEnacted.moment || result.lastSuggested && result.lastSuggested.moment;

    result.status = momentsToLoopStatus({
      enacted: result.lastEnacted && result.lastEnacted.moment
      , notEnacted: result.lastNotEnacted && result.lastNotEnacted.moment
      , suggested: result.lastSuggested && result.lastSuggested.moment
    }, false, recent);

    return result;
  };

  openaps.getEventTypes = function getEventTypes ( ) {
    return [
      {
        val: 'OpenAPS Offline'
        , name: 'OpenAPS Offline'
        , bg: false, insulin: false, carbs: false, prebolus: false, duration: true, percent: false, absolute: false, profile: false, split: false
      }
    ];
  };

  openaps.checkNotifications = function checkNotifications(sbx) {
    var prefs = openaps.getPrefs(sbx);

    if (!prefs.enableAlerts) { return; }

    var prop = sbx.properties.openaps;

    if (!prop.lastLoopMoment) {
      console.info('OpenAPS hasn\'t reported a loop yet');
      return;
    }

    var now = moment();
    var level = statusLevel(prop, prefs, sbx);
    if (level >= levels.WARN) {
      sbx.notifications.requestNotify({
        level: level
        , title: 'OpenAPS isn\'t looping'
        , message: 'Last Loop: ' + formatAgo(prop.lastLoopMoment, now.valueOf())
        , pushoverSound: 'echo'
        , plugin: openaps
        , debug: prop
      });
    }
  };

  openaps.findOfflineMarker = function findOfflineMarker(sbx) {
    return _.findLast(sbx.data.treatments, function match(treatment) {
      var eventTime = sbx.entryMills(treatment);
      var eventEnd = treatment.duration ? eventTime + times.mins(treatment.duration).msecs : eventTime;
      return eventTime <= sbx.time && treatment.eventType === 'OpenAPS Offline' && eventEnd >= sbx.time;
    });
  };

  openaps.updateVisualisation = function updateVisualisation (sbx) {
    var prop = sbx.properties.openaps;

    var prefs = openaps.getPrefs(sbx);

    function valueString (prefix, value) {
      return value ? prefix + value : '';
    }

    var events = [ ];

    function addSuggestion() {
      if (prop.lastSuggested) {

        var valueParts = [
          valueString('BG: ', prop.lastSuggested.bg)
          , valueString(', ', prop.lastSuggested.reason)
          , prop.lastSuggested.mealAssist ? ' Meal Assist: ' + prop.lastSuggested.mealAssist : ''
        ];

        valueParts = concatIOB(valueParts);

        events.push({
          time: prop.lastSuggested.moment
          , value: valueParts.join('')
        });
      }
    }

    function concatIOB (valueParts) {
      if (prop.lastIOB) {
        var bolussnooze = prop.lastIOB.bolussnooze || prop.lastIOB.bolusiob;
        valueParts = valueParts.concat([
          ' IOB: '
          , sbx.roundInsulinForDisplayFormat(prop.lastIOB.iob) + 'U'
          , prop.lastIOB.basaliob ? ', Basal IOB ' + sbx.roundInsulinForDisplayFormat(prop.lastIOB.basaliob) + 'U' : ''
          , bolussnooze ? ', Bolus Snooze ' + sbx.roundInsulinForDisplayFormat(bolussnooze) + 'U' : ''
        ]);
      }

      return valueParts;
    }

    if ('enacted' === prop.status.code) {
      var canceled = prop.lastEnacted.rate === 0 && prop.lastEnacted.duration === 0;

      var valueParts = [
        valueString('BG: ', prop.lastEnacted.bg)
        , ', Temp Basal' + (canceled ? ' Canceled' : ' Started') + ''
        , canceled ? '' : ' ' + prop.lastEnacted.rate.toFixed(2) + ' for ' + prop.lastEnacted.duration + 'm'
        , valueString(', ', prop.lastEnacted.reason)
        , prop.lastEnacted.mealAssist ? ' Meal Assist: ' + prop.lastEnacted.mealAssist : ''
      ];

      if (prop.lastSuggested && prop.lastSuggested.moment.isAfter(prop.lastEnacted.moment)) {
        addSuggestion();
      } else {
        valueParts = concatIOB(valueParts);
      }

      events.push({
        time: prop.lastEnacted.moment
        , value: valueParts.join('')
      });
    } else {
      addSuggestion();
    }

    _.forIn(prop.seenDevices, function seenDevice (device) {
      events.push({
        time: device.status.when
        , value: ['update from', device.name, device.status.symbol, device.status.label].join(' ')
      });
    });

    var sorted = _.sortBy(events, function toMill(event) {
      return event.time.valueOf();
    }).reverse();

    var info = _.map(sorted, function eventToInfo (event) {
      return {
        label: timeAt(false, sbx) + timeFormat(event.time, sbx)
        , value: event.value
      };
    });

    sbx.pluginBase.updatePillText(openaps, {
      value: timeFormat(prop.lastLoopMoment, sbx)
      , label: 'OpenAPS ' + prop.status.symbol
      , info: info
      , pillClass: statusClass(prop, prefs, sbx)
    });
  };

  function statusClass (prop, prefs, sbx) {
    var level = statusLevel(prop, prefs, sbx);
    var cls = 'current';

    if (level === levels.WARN) {
      cls = 'warn';
    } else if (level === levels.URGENT) {
      cls = 'urgent';
    }

    return cls;
  }

  function statusLevel (prop, prefs, sbx) {
    var level = levels.NONE;
    var now = moment(sbx.time);

    if (openaps.findOfflineMarker(sbx)) {
      console.info('OpenAPS known offline, not checking for alerts');
    } else if (prop.lastLoopMoment) {
      var urgentTime = prop.lastLoopMoment.clone().add(prefs.urgent, 'minutes');
      var warningTime = prop.lastLoopMoment.clone().add(prefs.warn, 'minutes');

      if (urgentTime.isBefore(now)) {
        level = levels.URGENT;
      } else if (warningTime.isBefore(now)) {
        level = levels.WARN;
      }
    }

    return level;
  }

  return openaps;

}

function timeFormat (m, sbx) {

  var when;
  if (m && sbx.data.inRetroMode) {
    when = m.format('LT');
  } else if (m) {
    when = formatAgo(m, sbx.time);
  } else {
    when = 'unknown';
  }

  return when;
}

function formatAgo (m, nowMills) {
  var ago = timeago.calcDisplay({mills: m.valueOf()}, nowMills);
  return (ago.value ? ago.value : '') + ago.shortLabel + (ago.shortLabel.length === 1 ? ' ago' : '');
}

function timeAt (prefix, sbx) {
  return sbx.data.inRetroMode ? (prefix ? ' ' : '') + '@ ' : (prefix ? ', ' : '');
}

module.exports = init;<|endoftext|>