<commit_before>/**
 * @project : modula.js
 * @package : core
 * @internal : tasks.ready.DOM
 * @type : function
 * @dependencies :
 *
 * @description :
 * ready is used to trigger the following 'ready' statements
 * - DOM
 * - DEMAND
 * - AJAX
 * - CUSTOM
 *
 * the DOM is always implemented, the other ready statements
 * should be implemented by the 'conceptixx - modula.php'
 */

		/**
		 * add DOMContentLoaded as available ReadyHandler
		 */
		ReadyHandler['DOM'] = {
			ready : function( task , type ) {

				// define ready object
				var ready = ReadyHandler['DOM']

				/**
				 * if we have a not ready task
				 */
				if( task && task.ReadyStates[ 'DOM' ] === undefined ) {
console.log( "ready.add" );
					// add task's PushStack to ready for completition
					ready[ ( ready.count = ready.count >>> 0 ) ] = task;
					// add readyTrigger to task's Stack
					PushStack[ task.PushStack ].push( [ function(){ return task.ReadyStates[ 'DOM' ]; } ] );
					// set ReadyStates for DOM ready
					task.ReadyStates[ 'DOM' ] = UseStates.progress;
					// increase count for next turn
					ready.count++;
				};

				/**
				 * check if we have already completed this ready check
				 */
				if( document.readyState === UseStates.complete && ready.count >>> 0 > 0 ) {
console.log( "ready.solve" );
					// define some variables
					var i = 0 , task;
					// loop the counts
					while( ready[ i ] !== undefined ) {
						// get task from ready[ i ]
						task = ready[ i ];
						// set readyState as complete for this task
						task.ReadyStates[ 'DOM' ] = UseStates.complete;
						// execute task
						tasks.resolve( task );
						// delete ready[ i ] in fact of execution and increase counter
						delete ready[ i++ ];
					};
					// reset ready.count
					ready.count = 0;
					// we are done sucessfully
					return true;
				};

				/**
				 * check the DOMContentLoaded so we catch cases were ready is called after ready
				 */
				if( ready.state === UseStates.undefine ) {
					// set eventHandler
					ready.addEvent();
				};

				/**
				 * if we have completed the eventHandler but not state complete
				 */
				if( ready.state === UseStates.complete ) {
					// use a timeout to handle this
					window.setTimeout( ready.ready );
				}

				/**
				 * return false when ready check has not been completed
				 */
				return false;
			},

			/**
			 * set DOMContentLoaded.state to undefine (not used yet)
			 */
			state : UseStates.undefine,

			/**
			 * addEvent
			 */
			addEvent : function() {
				// define ready object
				var ready = ReadyHandler['DOM']
				// add eventHandler aswell as Fallback
				document.addEventListener( "DOMContentLoaded" , ready.completed , false );
				window.addEventListener( "load" , ready.completed , false );
				// set ready.state as progress
				ready.state = UseStates.progress;
			},

			/**
			 * completed
			 */
			completed : function() {
				// define ready object
				var ready = ReadyHandler['DOM']
				// check if we have set eventHandler
				if( ready.state === UseStates.progress ) {
					// remove eventHandler
					document.removeEventListener( "DOMContentLoaded" , ready.completed , false );
					window.removeEventListener( "load" , ready.completed , false );
					// set ready.state as complete
					ready.state = UseStates.complete;
					// re-run the ready fuction
					ready.ready();
				};
			},

		};

<commit_msg>Update DOMContentLoaded.js<commit_after>/**
 * @project : modula.js
 * @package : core
 * @internal : tasks.ready.DOM
 * @type : function
 * @dependencies :
 *
 * @description :
 * ready is used to trigger the following 'ready' statements
 * - DOM
 * - DEMAND
 * - AJAX
 * - CUSTOM
 * - SELECT
 *
 * the DOM is always implemented, the other ready statements
 * should be implemented by the 'conceptixx - modula.php'
 */

		/**
		 * add DOM as available ReadyHandler
		 */
		ReadyHandler[ 'DOM' ] = {

			/**
			 * define ready
			 */
			ready : function( task , type ) {

				// define ready object
				var ready = ReadyHandler[ 'DOM' ];

				/**
				 * if we have a not ready task
				 */
				if( task && task.ReadyStates[ 'DOM' ] === undefined ) {
console.log( "ready.add" );
					// add task's PushStack to ready for completition
					ready[ ( ready.count = ready.count >>> 0 ) ] = task;
					// add readyTrigger to task's Stack
					PushStack[ task.PushStack ].push( [ function(){ return task.ReadyStates[ 'DOM' ]; } ] );
					// set ReadyStates for DOM ready
					task.ReadyStates[ 'DOM' ] = UseStates.progress;
					// increase count for next turn
					ready.count++;
				};

				/**
				 * check if we have already completed this ready check
				 */
				if( document.readyState === UseStates.complete && ready.count >>> 0 > 0 ) {
console.log( "ready.solve" );
					// define some variables
					var i = 0 , task;
					// loop the counts
					while( ready[ i ] !== undefined ) {
						// get task from ready[ i ]
						task = ready[ i ];
						// set readyState as complete for this task
						task.ReadyStates[ 'DOM' ] = UseStates.complete;
						// execute task
						tasks.resolve( task );
						// delete ready[ i ] in fact of execution and increase counter
						delete ready[ i++ ];
					};
					// reset ready.count
					ready.count = 0;
					// we are done sucessfully
					return true;
				};

				/**
				 * add eventHandler if needed
				 */
				if( ready.state === UseStates.undefine ) {
					// set eventHandler
					ready.addEvent();
				};

				/**
				 * if we have completed the ready event
				 */
				if( ready.state === UseStates.complete ) {
					// use a timeout to handle this
					window.setTimeout( ready.ready );
				}

				/**
				 * return false when ready check has not been completed
				 */
				return false;
			},

			/**
			 * set DOMContentLoaded.state to undefine (not used yet)
			 */
			state : UseStates.undefine,

			/**
			 * addEvent
			 */
			addEvent : function() {
				// define ready object
				var ready = ReadyHandler[ 'DOM' ]
				// add eventHandler aswell as Fallback
				document.addEventListener( 'DOMContentLoaded' , ready.completed , false );
				window.addEventListener( 'load' , ready.completed , false );
				// set ready.state as progress
				ready.state = UseStates.progress;
			},

			/**
			 * completed
			 */
			completed : function() {
				// define ready object
				var ready = ReadyHandler[ 'DOM' ]
				// check if we have set eventHandler
				if( ready.state === UseStates.progress ) {
					// remove eventHandler
					document.removeEventListener( 'DOMContentLoaded' , ready.completed , false );
					window.removeEventListener( 'load' , ready.completed , false );
					// set ready.state as complete
					ready.state = UseStates.complete;
					// re-run the ready fuction
					ready.ready();
				};
			},

		};

<|endoftext|>


<commit_before>var url = require("url");
var fs = require("fs");
var resource = require("./resource");
var scriptInjectionProcessor = require("./processors/script-injector");
var proxyMiddleware = require("../http-proxy");

module.exports = {
    create: function (data) {
        var resourceSet = Object.create(this);

        resourceSet.resources = {};
        resourceSet.load = data.load || [];
        resourceSet.rootResource = data.rootResource;
        resourceSet.contextPath = data.contextPath || "";

        for (var key in data.resources) {
            resourceSet.addResource(key, data.resources[key]);
        }

        resourceSet.setUpRootResource();

        return resourceSet;
    },

    addResource: function (path, data) {
        var r = resource.create(path, data);
        this.resources[path] = r;

        if (path == "/") {
            if (!("Content-Type" in r.headers)) {
                r.headers["Content-Type"] = "text/html";
            }

            var p = Object.create(scriptInjectionProcessor);
            p.scripts = this.rootResourceScripts();
            r.addProcessor(p);
        }
    },

    addFile: function (path) {
        this.addResource(path, {
            content: function (promise) {
                fs.readFile(path, function (err, data) {
                    if (err) {
                        promise.reject(err);
                    } else {
                        promise.resolve(data);
                    }
                });
            }
        });
    },

    setUpRootResource: function () {
        if (!("/" in this.resources)) {
            this.assignDefaultRootResource();
        }
    },

    assignDefaultRootResource: function () {
        this.addResource("/", {
            content: "<!DOCTYPE html>"
        });
    },

    /*
     * Takes the data for a resource set. Returns a string with an error message, or
     * nothing if there was no error.
     */
    validate: function (data) {
        if (!data.hasOwnProperty("resources")) {
            return "Missing property 'resources'.";
        }

        if (!data.hasOwnProperty("load")) {
            return "Missing property 'load'.";
        }

        for (var i = 0, ii = data.load.length; i < ii; i++) {
            var resourceFound = false;
            for (var resource in data.resources) {
                if (data.load[i] == resource) resourceFound = true;
            }

            if (!resourceFound) {
                return "'load' entry '" + data.load[i] + "' missing corresponding 'resources' entry.";
            }
        }

        for (var resource in data.resources) {
            if ("content" in data.resources[resource]) {
                if (data.resources[resource].content instanceof Buffer) {
                    continue;
                }

                if (typeof(data.resources[resource].content) == "string") {
                    continue;
                } else {
                    return "The resource '" + resource + "' was not a string."
                }
            }
        }
    },

    respond: function (req, res) {
        var path, resource;

        for (path in this.resources) {
            resource = this.getResource(path);

            if (this.serveResourceContent(path, resource, req, res)) return true;
            if (this.serveProxyRequest(path, resource, req, res)) return true;
        }
    },

    serveResourceContent: function (path, resource, req, res) {
        if (this.resourceContextPath() + path != req.url) {
            return;
        }

        var promise = resource.getContent();
        promise.then(function (content) {
            res.writeHead(200, resource.getHeaders());
            res.write(content);
            res.end();
        }, function (err) {
            res.writeHead(500);
            res.write(err.toString());
            res.end();
        });

        return true;
    },

    serveProxyRequest: function (path, resource, req, res) {
        if (!resource.proxy ||
            req.url.indexOf(this.resourceContextPath() + path) != 0) {
            return;
        }

        resource.proxy.respond(req, res);
        return true;
    },

    rootResourceScripts: function () {
        var scripts = [];

        for (var i = 0, ii = this.load.length; i < ii; i++) {
            scripts.push(this.resourceContextPath() + this.load[i]);
        }

        return scripts;
    },

    getResource: function (path) {
        var resource = this.resources[path];

        if (resource.backend && !resource.proxy) {
            var parsed = url.parse(resource.backend);
            resource.proxy = proxyMiddleware.create(
                parsed.hostname, parsed.port, parsed.path);
            resource.proxy.proxyPath = this.resourceContextPath();
        }

        if (resource.combine && !resource.content) {
            resource.content = this.combineResources(resource.combine);
        }

        return resource;
    },

    combineResources: function (resources) {
        var content = "";

        for (var i = 0, l = resources.length; i < l; ++i) {
            content += this.getResource(resources[i]).content + "\n";
        }

        return content;
    },

    resourceContextPath: function () {
        return this.contextPath + "/res";
    }
};
<commit_msg>Removing resource set context path suffix.<commit_after>var url = require("url");
var fs = require("fs");
var resource = require("./resource");
var scriptInjectionProcessor = require("./processors/script-injector");
var proxyMiddleware = require("../http-proxy");

module.exports = {
    create: function (data) {
        var resourceSet = Object.create(this);

        resourceSet.resources = {};
        resourceSet.load = data.load || [];
        resourceSet.rootResource = data.rootResource;
        resourceSet.contextPath = data.contextPath || "";

        for (var key in data.resources) {
            resourceSet.addResource(key, data.resources[key]);
        }

        resourceSet.setUpRootResource();

        return resourceSet;
    },

    addResource: function (path, data) {
        var r = resource.create(path, data);
        this.resources[path] = r;

        if (path == "/") {
            if (!("Content-Type" in r.headers)) {
                r.headers["Content-Type"] = "text/html";
            }

            var p = Object.create(scriptInjectionProcessor);
            p.scripts = this.rootResourceScripts();
            r.addProcessor(p);
        }
    },

    addFile: function (path) {
        this.addResource(path, {
            content: function (promise) {
                fs.readFile(path, function (err, data) {
                    if (err) {
                        promise.reject(err);
                    } else {
                        promise.resolve(data);
                    }
                });
            }
        });
    },

    setUpRootResource: function () {
        if (!("/" in this.resources)) {
            this.assignDefaultRootResource();
        }
    },

    assignDefaultRootResource: function () {
        this.addResource("/", {
            content: "<!DOCTYPE html>"
        });
    },

    /*
     * Takes the data for a resource set. Returns a string with an error message, or
     * nothing if there was no error.
     */
    validate: function (data) {
        if (!data.hasOwnProperty("resources")) {
            return "Missing property 'resources'.";
        }

        if (!data.hasOwnProperty("load")) {
            return "Missing property 'load'.";
        }

        for (var i = 0, ii = data.load.length; i < ii; i++) {
            var resourceFound = false;
            for (var resource in data.resources) {
                if (data.load[i] == resource) resourceFound = true;
            }

            if (!resourceFound) {
                return "'load' entry '" + data.load[i] + "' missing corresponding 'resources' entry.";
            }
        }

        for (var resource in data.resources) {
            if ("content" in data.resources[resource]) {
                if (data.resources[resource].content instanceof Buffer) {
                    continue;
                }

                if (typeof(data.resources[resource].content) == "string") {
                    continue;
                } else {
                    return "The resource '" + resource + "' was not a string."
                }
            }
        }
    },

    respond: function (req, res) {
        var path, resource;

        for (path in this.resources) {
            resource = this.getResource(path);

            if (this.serveResourceContent(path, resource, req, res)) return true;
            if (this.serveProxyRequest(path, resource, req, res)) return true;
        }
    },

    serveResourceContent: function (path, resource, req, res) {
        if (this.contextPath + path != req.url) {
            return;
        }

        var promise = resource.getContent();
        promise.then(function (content) {
            res.writeHead(200, resource.getHeaders());
            res.write(content);
            res.end();
        }, function (err) {
            res.writeHead(500);
            res.write(err.toString());
            res.end();
        });

        return true;
    },

    serveProxyRequest: function (path, resource, req, res) {
        if (!resource.proxy ||
            req.url.indexOf(this.contextPath + path) != 0) {
            return;
        }

        resource.proxy.respond(req, res);
        return true;
    },

    rootResourceScripts: function () {
        var scripts = [];

        for (var i = 0, ii = this.load.length; i < ii; i++) {
            scripts.push(this.contextPath + this.load[i]);
        }

        return scripts;
    },

    getResource: function (path) {
        var resource = this.resources[path];

        if (resource.backend && !resource.proxy) {
            var parsed = url.parse(resource.backend);
            resource.proxy = proxyMiddleware.create(
                parsed.hostname, parsed.port, parsed.path);
            resource.proxy.proxyPath = this.contextPath;
        }

        if (resource.combine && !resource.content) {
            resource.content = this.combineResources(resource.combine);
        }

        return resource;
    },

    combineResources: function (resources) {
        var content = "";

        for (var i = 0, l = resources.length; i < l; ++i) {
            content += this.getResource(resources[i]).content + "\n";
        }

        return content;
    }
};
<|endoftext|>


<commit_before>import { L10nError } from './errors';

const KNOWN_MACROS = ['PLURAL'];
const MAX_PLACEABLE_LENGTH = 2500;

// Unicode bidi isolation characters
const FSI = '\u2068';
const PDI = '\u2069';

// Helper functions for inserting a placeable value into a string

function stringify(res, value) {
  if (typeof value === 'string') {
    return FSI + value + PDI;
  }

  if (typeof value === 'number' && !isNaN(value)) {
    const formatter = res.ctx._getNumberFormatter(res.lang);
    return formatter.format(value);
  }

  if (typeof value === 'function') {
    return FSI + '{function}' + PDI;
  }
}

function stringifyList(res, list) {
  const values = list.map(
    elem => stringify(res, elem)
  );

  // the most common scenario; avoid creating a ListFormat instance
  if (values.length === 1) {
    return values[0];
  }

  const formatter = res.ctx._getListFormatter(res.lang);
  return formatter.format(values);
}

// Helper functions for picking the right member from an array of traits or
// variants.

function chooseTrait(entity, name) {
  for (let trait of entity.traits) {

    if (name === trait.key || (!name && trait.default)) {
      return trait;
    }
  }
}

// resolve* functions can throw and return a single value

function resolve(res, expr) {
  // XXX remove
  if (typeof expr === 'string') {
    return expr;
  }

  switch (expr.type) {
    case 'EntityReference':
      return resolveEntity(res, expr);
    case 'Variable':
      return resolveVariable(res, expr);
    // XXX case 'Keyword':
    //  return resolveKeyword(res, expr);
    case 'Number':
      return resolveNumber(res, expr);
    case 'CallExpression':
      return resolveCall(res, expr);
    case 'MemberExpression':
      return resolveTrait(res, expr);
    default:
      throw new L10nError('Unknown placeable type');
  }
}

function resolveEntity(res, expr) {
  const id = expr.id;
  const entity = res.ctx._getEntity(res.lang, id);

  if (!entity) {
    throw new L10nError('Unknown reference: ' + id);
  }

  if (res.dirty.has(entity)) {
    throw new L10nError('Cyclic reference: ' + id);
  }

  const value = entity.value !== null ?
    entity.value :
    chooseTrait(entity).value;

  return resolveValue(res, value);
}

function resolveVariable(res, expr) {
  const id = expr.id;
  const args = res.args;

  if (args && args.hasOwnProperty(id)) {
    return args[id];
  }

  throw new L10nError('Unknown reference: ' + id);
}

function resolveKeyword(res, expr) {
  return expr.value;
}

function resolveNumber(res, expr) {
  return parseInt(expr.value);
}

function resolveCall(res, expr) {
  const id = expr.callee.name;

  if (KNOWN_MACROS.indexOf(id) === -1) {
    throw new L10nError('Unknown reference: ' + id);
  }

  const callee = res.ctx._getMacro(res.lang, id);
  return callee(
    ...expr.args.map(
      arg => resolve(res, arg)
    )
  );
}

function resolveTrait(res, expr) {
  const id = expr.idref.name;
  const key = expr.keyword;
  const entity = res.ctx._getEntity(res.lang, id);

  if (!entity) {
    throw new L10nError('Unknown entity: ' + id);
  }

  const trait = chooseTrait(entity, key);

  if (!trait) {
    throw new L10nError('Unknown trait: ' + key);
  }

  return resolveValue(res, trait.value);
}

function resolveVariant(res, variants, expr) {
  if (typeof expr === 'function') {
    for (let variant of variants) {
      if (expr(resolve(res, variant.key))) {
        return resolveValue(res, variant.value);
      }
    }
  } else {
    for (let variant of variants) {
      if (expr === resolve(res, variant.key)) {
        return resolveValue(res, variant.value);
      }
    }
  }

  for (let variant of variants) {
    if (variant.default) {
      return resolveValue(res, variant.value);
    }
  }

  throw new L10nError('No default variant found');
}

function resolvePlaceableExpression(res, expression) {
  const expr = resolve(res, expression.expression);

  const value = expression.variants === null ?
    expr :
    resolveVariant(res, expression.variants, expr);

  if (value.length >= MAX_PLACEABLE_LENGTH) {
    throw new L10nError(
      'Too many characters in placeable (' + value.length +
        ', max allowed is ' + MAX_PLACEABLE_LENGTH + ')'
    );
  }

  return value;
}

function resolvePlaceable(res, placeable) {
  return placeable.expressions.map(
    expr => resolvePlaceableExpression(res, expr)
  );
}

function resolveValue(res, value) {
  const [errs, str] = formatValue(res, value);

  if (errs.length) {
    throw new L10nError('Broken value.');
  }

  return str;
}

// formatValue collects any errors and return them as the first element of
// the return tuple: [errors, value]

function formatValue(res, value) {
  return value.elements.reduce(([errs, seq], elem) => {
    if (elem.type === 'TextElement') {
      return [errs, seq + elem.value];
    } else if (elem.type === 'Placeable') {
      try {
        return [errs, seq + stringifyList(res, resolvePlaceable(res, elem))];
      } catch(e) {
        return [[...errs, e], seq + stringify(res, '{}')];
      }
    } else {
      return [
        [...errs, new L10nError('Unresolvable value')],
        seq + stringify(res, '{}')
      ];
    }
  }, [[], '']);
}

export function format(ctx, lang, args, entity) {
  const res = {
    ctx,
    lang,
    args,
    errors: [],
    dirty: new WeakSet([entity])
  };

  const value = entity.value !== null ?
    entity.value :
    chooseTrait(entity).value;

  return formatValue(res, value);
}
<commit_msg>getEntityValue<commit_after>import { L10nError } from './errors';

const KNOWN_MACROS = ['PLURAL'];
const MAX_PLACEABLE_LENGTH = 2500;

// Unicode bidi isolation characters
const FSI = '\u2068';
const PDI = '\u2069';

// Helper functions for inserting a placeable value into a string

function stringify(res, value) {
  if (typeof value === 'string') {
    return FSI + value + PDI;
  }

  if (typeof value === 'number' && !isNaN(value)) {
    const formatter = res.ctx._getNumberFormatter(res.lang);
    return formatter.format(value);
  }

  if (typeof value === 'function') {
    return FSI + '{function}' + PDI;
  }
}

function stringifyList(res, list) {
  const values = list.map(
    elem => stringify(res, elem)
  );

  // the most common scenario; avoid creating a ListFormat instance
  if (values.length === 1) {
    return values[0];
  }

  const formatter = res.ctx._getListFormatter(res.lang);
  return formatter.format(values);
}

// Helper for choosing entity value

function getEntityValue(entity) {
  if (entity.value !== null) {
    return entity.value;
  }

  for (let trait of entity.traits) {
    if (trait.default) {
      return trait.value;
    }
  }

  return null;
}

// resolve* functions can throw and return a single value

function resolve(res, expr) {
  // XXX remove
  if (typeof expr === 'string') {
    return expr;
  }

  switch (expr.type) {
    case 'EntityReference':
      return resolveEntityReference(res, expr);
    case 'Variable':
      return resolveVariable(res, expr);
    // XXX case 'Keyword':
    //  return resolveKeyword(res, expr);
    case 'Number':
      return resolveNumber(res, expr);
    case 'CallExpression':
      return resolveCall(res, expr);
    case 'MemberExpression':
      return resolveMember(res, expr);
    default:
      throw new L10nError('Unknown placeable type');
  }
}

function resolveEntity(res, id) {
  const entity = res.ctx._getEntity(res.lang, id);

  if (!entity) {
    throw new L10nError('Unknown entity: ' + id);
  }

  if (res.dirty.has(entity)) {
    throw new L10nError('Cyclic reference: ' + id);
  }

  res.dirty.add(entity);
  return entity;

}

function resolveEntityReference(res, expr) {
  const id = expr.id;
  const entity = resolveEntity(res, id);
  const value = getEntityValue(entity);

  if (value === null) {
    throw new L10nError('No value: ' + id);
  }

  return resolveValue(res, value);
}

function resolveVariable(res, expr) {
  const id = expr.id;
  const args = res.args;

  if (args && args.hasOwnProperty(id)) {
    return args[id];
  }

  throw new L10nError('Unknown reference: ' + id);
}

function resolveKeyword(res, expr) {
  return expr.value;
}

function resolveNumber(res, expr) {
  return parseInt(expr.value);
}

function resolveCall(res, expr) {
  const id = expr.callee.name;

  if (KNOWN_MACROS.indexOf(id) === -1) {
    throw new L10nError('Unknown reference: ' + id);
  }

  const callee = res.ctx._getMacro(res.lang, id);
  return callee(
    ...expr.args.map(
      arg => resolve(res, arg)
    )
  );
}

function resolveTrait(res, traits, key) {
  for (let trait of traits) {
    if (key === resolve(res, trait.key)) {
      return trait;
    }
  }

  throw new L10nError('Unknown trait: ' + key);
}

function resolveMember(res, expr) {
  const id = expr.idref.id;
  const key = expr.keyword;
  const entity = resolveEntity(res, id);

  return resolveValue(
    res, resolveTrait(res, entity.traits, key).value
  );
}

function resolveVariant(res, variants, expr) {
  if (typeof expr === 'function') {
    for (let variant of variants) {
      if (expr(resolve(res, variant.key))) {
        return variant;
      }
    }
  } else {
    for (let variant of variants) {
      if (expr === resolve(res, variant.key)) {
        return variant;
      }
    }
  }

  for (let variant of variants) {
    if (variant.default) {
      return variant;
    }
  }

  throw new L10nError('No default variant found');
}

function resolvePlaceableExpression(res, expression) {
  const expr = resolve(res, expression.expression);

  const value = expression.variants === null ?
    expr :
    resolveValue(
      res, resolveVariant(res, expression.variants, expr).value
    );

  if (value.length >= MAX_PLACEABLE_LENGTH) {
    throw new L10nError(
      'Too many characters in placeable (' + value.length +
        ', max allowed is ' + MAX_PLACEABLE_LENGTH + ')'
    );
  }

  return value;
}

function resolvePlaceable(res, placeable) {
  return placeable.expressions.map(
    expr => resolvePlaceableExpression(res, expr)
  );
}

function resolveValue(res, value) {
  const [errs, str] = formatValue(res, value);

  if (errs.length) {
    throw new L10nError('Broken value.');
  }

  return str;
}

// formatValue collects any errors and return them as the first element of
// the return tuple: [errors, value]

function formatValue(res, value) {
  return value.elements.reduce(([errs, seq], elem) => {
    if (elem.type === 'TextElement') {
      return [errs, seq + elem.value];
    } else if (elem.type === 'Placeable') {
      try {
        return [errs, seq + stringifyList(res, resolvePlaceable(res, elem))];
      } catch(e) {
        return [[...errs, e], seq + stringify(res, '{}')];
      }
    } else {
      return [
        [...errs, new L10nError('Unresolvable value')],
        seq + stringify(res, '{}')
      ];
    }
  }, [[], '']);
}

export function format(ctx, lang, args, entity) {
  const res = {
    ctx,
    lang,
    args,
    errors: [],
    dirty: new WeakSet([entity])
  };

  const value = getEntityValue(entity);

  if (value === null) {
    const err = new L10nError('No value: ' + entity.id);
    return [[err], null];
  }

  return formatValue(res, value);
}
<|endoftext|>


<commit_before>/**
 * @license
 * responsively.js
 * Copyright 2014 Mark Bradley, Ted Halmrast
 * Available under MIT license <http://responsivelyjs.org/license>
 */
;(function(window) {

  function result(object, property) {
    if (object == null)
      return void 0;

    var value = object[property];
    return typeof value === 'function' ? value.call( object ) : value;
  };

  function responsively( opts ) {

    var p         = $( result( opts, 'parent' ) ),
        rules     = result( opts, 'rules' ),
        constrain = result( opts, 'constrain' ),
        cel, // cel = Constrained ELement
        tHeight   = result( opts, 'height' );

    var outer = result( opts, 'outer' );
    if ( outer )
      p.replaceWith( outer );

    var inner = result( opts, 'inner' );
    if ( inner )
      p.html( inner );

    p = $( opts.parent );

    var r = {
      render: function() {
        r.level = 0;

        cel = constrain ? p.find( constrain ) : p;
        var width = cel.width();

        if ( !tHeight )
          tHeight = cel.height();

        //console.log( 'est width:' + width + ' target height:' + tHeight );

        this.update();
      },

      update: function() {
        cel.css( 'visibility', 'hidden' );

        for (;;) {

          //console.log( 'processing level ' + r.level + " cel.height:" + cel.height() + " tHeight:" + tHeight );

          if ( r.level && // always process level 0
               cel.height() <= tHeight )
            break;

          var found = false;

          for ( var li=0; li<rules.length; li++ ) {
            var l      = rules[ li ],
                a      = p.find( result( l, 'a' ) ),
                tlevel = result( l, 'level' ) || 0;

            if ( l.when !== undefined && !result( l, 'when' ) )
              continue;

            if ( tlevel === r.level ) {
              var cand;

              var fn = result( l, 'fn' );
              if ( fn )
                fn();

              if ( result( l, 'hide' ) )
                a.hide();

              var inner = result( l, 'inner' );
              if ( inner !== undefined )
                a.html( inner ? inner : '' );

              var outer = result( l, 'outer' );
              if ( outer !== undefined ) {
                if ( outer )
                  a.replaceWith( outer );
                else
                  a.remove();
              }

              found = true;
            }
          }

          r.level++;

          if ( !found && tlevel )
            break;

          //console.log( 'cel.height:' + cel.height() + ' target height:' + tHeight + ' found:' + found );
        }

        cel.css( 'visibility', 'visible' );
      }
    };

    return r;
  };

  if ( typeof define == 'function' && typeof define.amd == 'object' && define.amd ) {
    define(function() {
      return responsively;
    });
  } else {
    window.responsively = responsively;
  }
}(this));

<commit_msg>added css, addClass, removeClass options<commit_after>/**
 * @license
 * responsively.js
 * Copyright 2014 Mark Bradley, Ted Halmrast
 * Available under MIT license <http://responsivelyjs.org/license>
 */
;(function(window) {

  function result(object, property) {
    if (object == null)
      return void 0;

    var value = object[property];
    return typeof value === 'function' ? value.call( object ) : value;
  };

  function responsively( opts ) {

    var p         = $( result( opts, 'parent' ) ),
        rules     = result( opts, 'rules' ),
        constrain = result( opts, 'constrain' ),
        cel, // cel = Constrained ELement
        tHeight   = result( opts, 'height' );

    var outer = result( opts, 'outer' );
    if ( outer )
      p.replaceWith( outer );

    var inner = result( opts, 'inner' );
    if ( inner )
      p.html( inner );

    p = $( opts.parent );

    var r = {
      render: function() {
        r.level = 0;

        cel = constrain ? p.find( constrain ) : p;
        var width = cel.width();

        if ( !tHeight )
          tHeight = cel.height();

        //console.log( 'est width:' + width + ' target height:' + tHeight );

        this.update();
      },

      update: function() {
        cel.css( 'visibility', 'hidden' );

        for (;;) {

          //console.log( 'processing level ' + r.level + " cel.height:" + cel.height() + " tHeight:" + tHeight );

          if ( r.level && // always process level 0
               cel.height() <= tHeight )
            break;

          var found = false;

          for ( var li=0; li<rules.length; li++ ) {
            var l      = rules[ li ],
                a      = p.find( result( l, 'a' ) ),
                tlevel = result( l, 'level' ) || 0;

            if ( l.when !== undefined && !result( l, 'when' ) )
              continue;

            if ( tlevel === r.level ) {
              var cand;

              var fn = result( l, 'fn' );
              if ( fn )
                fn();

              if ( result( l, 'hide' ) )
                a.hide();

              var inner = result( l, 'inner' );
              if ( inner !== undefined )
                a.html( inner ? inner : '' );

              var outer = result( l, 'outer' );
              if ( outer !== undefined ) {
                if ( outer )
                  a.replaceWith( outer );
                else
                  a.remove();
              }

              var css = result( l, 'css' );
              if ( css )
                a.css( css );

              var addClass = result( l, 'addClass' );
              if ( addClass )
                a.addClass( addClass );

              var removeClass = result( l, 'removeClass' );
              if ( removeClass )
                a.removeClass( removeClass );

              found = true;
            }
          }

          r.level++;

          if ( !found && tlevel )
            break;

          //console.log( 'cel.height:' + cel.height() + ' target height:' + tHeight + ' found:' + found );
        }

        cel.css( 'visibility', 'visible' );
      }
    };

    return r;
  };

  if ( typeof define == 'function' && typeof define.amd == 'object' && define.amd ) {
    define(function() {
      return responsively;
    });
  } else {
    window.responsively = responsively;
  }
}(this));

<|endoftext|>


<commit_before>import gulp  from 'gulp';
import loadPlugins from 'gulp-load-plugins';
import del  from 'del';
import glob  from 'glob';
import path  from 'path';
import * as isparta  from 'isparta';
import babelify  from 'babelify';
import watchify  from 'watchify';
import buffer  from 'vinyl-buffer';
import esperanto  from 'esperanto';
import browserify  from 'browserify';
import runSequence  from 'run-sequence';
import source  from 'vinyl-source-stream';

import manifest  from './package.json';

// Load all of our Gulp plugins
const $ = loadPlugins();

// Gather the library data from `package.json`
const config = manifest.babelBoilerplateOptions;
const mainFile = manifest.main;
const destinationFolder = path.dirname(mainFile);
const exportFileName = path.basename(mainFile, path.extname(mainFile));

// Remove a directory
function _clean(dir, done) {
  del([dir], done);
}

function cleanDist(done) {
  _clean(destinationFolder, done)
}

function cleanTmp() {
  _clean('tmp', done)
}

// Send a notification when JSCS fails,
// so that you know your changes didn't build
function _jscsNotify(file) {
  if (!file.jscs) { return; }
  return file.jscs.success ? false : 'JSCS failed';
}

// Lint a set of files
function lint(files) {
  return gulp.src(files)
    .pipe($.plumber())
    .pipe($.eslint())
    .pipe($.eslint.format())
    .pipe($.eslint.failOnError())
    .pipe($.jscs())
    .pipe($.notify(_jscsNotify));
}

function lintSrc() {
  return lint('src/**/*.js');
}

function lintTest() {
  return lint('test/**/*.js');
}

function build(done) {
  esperanto.bundle({
    base: 'src',
    entry: config.entryFileName,
  }).then(bundle => {
    const res = bundle.toUmd({
      // Don't worry about the fact that the source map is inlined at this step.
      // `gulp-sourcemaps`, which comes next, will externalize them.
      sourceMap: 'inline',
      name: config.mainVarName
    });

    $.file(exportFileName + '.js', res.code, { src: true })
      .pipe($.plumber())
      .pipe($.sourcemaps.init({ loadMaps: true }))
      .pipe($.babel())
      .pipe($.sourcemaps.write('./'))
      .pipe(gulp.dest(destinationFolder))
      .pipe($.filter(['*', '!**/*.js.map']))
      .pipe($.rename(exportFileName + '.min.js'))
      .pipe($.sourcemaps.init({ loadMaps: true }))
      .pipe($.uglify())
      .pipe($.sourcemaps.write('./'))
      .pipe(gulp.dest(destinationFolder))
      .on('end', done);
  })
  .catch(done);
}

function _runBrowserifyBundle(bundler) {
  return bundler.bundle()
    .on('error', err => {
      console.log(err.message);
      this.emit('end');
    })
    .pipe($.plumber())
    .pipe(source('./tmp/__spec-build.js'))
    .pipe(buffer())
    .pipe(gulp.dest(''))
    .pipe($.livereload());
}

// Build the unit test suite for running tests
// in the browser
function _browserifyBundle() {
  // Our browserify bundle is made up of our unit tests, which
  // should individually load up pieces of our application.
  // We also include the browserify setup file.
  const testFiles = glob.sync('./test/unit/**/*.js');
  const allFiles = ['./test/setup/browserify.js'].concat(testFiles);

  // Create our bundler, passing in the arguments required for watchify
  let bundler = browserify(allFiles, {
    cache: {},
    packageCache: {},
    debug: true
  });

  // Watch the bundler, and re-bundle it whenever files change
  bundler = watchify(bundler);
  bundler.on('update', () => _runBrowserifyBundle(bundler));

  // Set up Babelify so that ES6 works in the tests
  bundler.transform(babelify.configure({
    sourceMapRelative: __dirname + '/src'
  }));

  return _runBrowserifyBundle(bundler);
}

function _mocha() {
  return gulp.src(['test/setup/node.js', 'test/unit/**/*.js'], {read: false})
    .pipe($.mocha({
      reporter: 'dot',
      globals: config.mochaGlobals,
      ignoreLeaks: false
    }));
}

function _registerBabel() {
  require('babel-core/register');
}

function test() {
  _registerBabel();
  return _mocha();
}

function coverage(done) {
  _registerBabel();
  gulp.src(['src/**/*.js'])
    .pipe($.istanbul({ instrumenter: isparta.Instrumenter }))
    .pipe($.istanbul.hookRequire())
    .on('finish', () => {
      return test()
        .pipe($.istanbul.writeReports())
        .on('end', done);
    });
}

// These are JS files that should be watched by Gulp. When running tests in the browser,
// watchify is used instead, so these aren't included.
const jsWatchFiles = ['src/**/*', 'test/**/*'];
// These are files other than JS files which are to be watched. They are always watched.
const otherWatchFiles = ['package.json', '**/.eslintrc', '.jscsrc'];

// Run the headless unit tests as you make changes.
function watch() {
  const watchFiles = jsWatchFiles.concat(otherWatchFiles);
  gulp.watch(watchFiles, ['test']);
}

function testBrowser() {
  // Ensure that linting occurs before browserify runs. This prevents
  // the build from breaking due to poorly formatted code.
  runSequence(['lint-src', 'lint-test'], () => {
    _browserifyBundle();
    $.livereload.listen({port: 35729, host: 'localhost', start: true});
    gulp.watch(otherWatchFiles, ['lint-src', 'lint-test']);
  });
}

// Remove the built files
gulp.task('clean', cleanDist);

// Remove our temporary files
gulp.task('clean-tmp', cleanTmp);

// Lint our source code
gulp.task('lint-src', lintSrc);

// Lint our test code
gulp.task('lint-test', lintTest);

// Build two versions of the library
gulp.task('build', ['lint-src', 'clean'], build);

// Lint and run our tests
gulp.task('test', ['lint-src', 'lint-test'], test);

// Set up coverage and run tests
gulp.task('coverage', ['lint-src', 'lint-test'], coverage);

// Set up a livereload environment for our spec runner `test/runner.html`
gulp.task('test-browser', testBrowser);

// Run the headless unit tests as you make changes.
gulp.task('watch', watch);

// An alias of test
gulp.task('default', ['test']);
<commit_msg>Prevents the browser tests from reloading on first build.<commit_after>import gulp  from 'gulp';
import loadPlugins from 'gulp-load-plugins';
import del  from 'del';
import glob  from 'glob';
import path  from 'path';
import * as isparta  from 'isparta';
import babelify  from 'babelify';
import watchify  from 'watchify';
import buffer  from 'vinyl-buffer';
import esperanto  from 'esperanto';
import browserify  from 'browserify';
import runSequence  from 'run-sequence';
import source  from 'vinyl-source-stream';

import manifest  from './package.json';

// Load all of our Gulp plugins
const $ = loadPlugins();

// Gather the library data from `package.json`
const config = manifest.babelBoilerplateOptions;
const mainFile = manifest.main;
const destinationFolder = path.dirname(mainFile);
const exportFileName = path.basename(mainFile, path.extname(mainFile));

// Remove a directory
function _clean(dir, done) {
  del([dir], done);
}

function cleanDist(done) {
  _clean(destinationFolder, done)
}

function cleanTmp() {
  _clean('tmp', done)
}

// Send a notification when JSCS fails,
// so that you know your changes didn't build
function _jscsNotify(file) {
  if (!file.jscs) { return; }
  return file.jscs.success ? false : 'JSCS failed';
}

// Lint a set of files
function lint(files) {
  return gulp.src(files)
    .pipe($.plumber())
    .pipe($.eslint())
    .pipe($.eslint.format())
    .pipe($.eslint.failOnError())
    .pipe($.jscs())
    .pipe($.notify(_jscsNotify));
}

function lintSrc() {
  return lint('src/**/*.js');
}

function lintTest() {
  return lint('test/**/*.js');
}

function build(done) {
  esperanto.bundle({
    base: 'src',
    entry: config.entryFileName,
  }).then(bundle => {
    const res = bundle.toUmd({
      // Don't worry about the fact that the source map is inlined at this step.
      // `gulp-sourcemaps`, which comes next, will externalize them.
      sourceMap: 'inline',
      name: config.mainVarName
    });

    $.file(exportFileName + '.js', res.code, { src: true })
      .pipe($.plumber())
      .pipe($.sourcemaps.init({ loadMaps: true }))
      .pipe($.babel())
      .pipe($.sourcemaps.write('./'))
      .pipe(gulp.dest(destinationFolder))
      .pipe($.filter(['*', '!**/*.js.map']))
      .pipe($.rename(exportFileName + '.min.js'))
      .pipe($.sourcemaps.init({ loadMaps: true }))
      .pipe($.uglify())
      .pipe($.sourcemaps.write('./'))
      .pipe(gulp.dest(destinationFolder))
      .on('end', done);
  })
  .catch(done);
}

function _runBrowserifyBundle(bundler) {
  return bundler.bundle()
    .on('error', err => {
      console.log(err.message);
      this.emit('end');
    })
    .pipe($.plumber())
    .pipe(source('./tmp/__spec-build.js'))
    .pipe(buffer())
    .pipe(gulp.dest(''))
    .pipe($.livereload());
}

// Build the unit test suite for running tests
// in the browser
function _browserifyBundle() {
  // Our browserify bundle is made up of our unit tests, which
  // should individually load up pieces of our application.
  // We also include the browserify setup file.
  const testFiles = glob.sync('./test/unit/**/*.js');
  const allFiles = ['./test/setup/browserify.js'].concat(testFiles);

  // Create our bundler, passing in the arguments required for watchify
  let bundler = browserify(allFiles, {
    cache: {},
    packageCache: {},
    debug: true
  });

  // Watch the bundler, and re-bundle it whenever files change
  bundler = watchify(bundler);
  bundler.on('update', () => _runBrowserifyBundle(bundler));

  // Set up Babelify so that ES6 works in the tests
  bundler.transform(babelify.configure({
    sourceMapRelative: __dirname + '/src'
  }));

  return _runBrowserifyBundle(bundler);
}

function _mocha() {
  return gulp.src(['test/setup/node.js', 'test/unit/**/*.js'], {read: false})
    .pipe($.mocha({
      reporter: 'dot',
      globals: config.mochaGlobals,
      ignoreLeaks: false
    }));
}

function _registerBabel() {
  require('babel-core/register');
}

function test() {
  _registerBabel();
  return _mocha();
}

function coverage(done) {
  _registerBabel();
  gulp.src(['src/**/*.js'])
    .pipe($.istanbul({ instrumenter: isparta.Instrumenter }))
    .pipe($.istanbul.hookRequire())
    .on('finish', () => {
      return test()
        .pipe($.istanbul.writeReports())
        .on('end', done);
    });
}

// These are JS files that should be watched by Gulp. When running tests in the browser,
// watchify is used instead, so these aren't included.
const jsWatchFiles = ['src/**/*', 'test/**/*'];
// These are files other than JS files which are to be watched. They are always watched.
const otherWatchFiles = ['package.json', '**/.eslintrc', '.jscsrc'];

// Run the headless unit tests as you make changes.
function watch() {
  const watchFiles = jsWatchFiles.concat(otherWatchFiles);
  gulp.watch(watchFiles, ['test']);
}

function testBrowser() {
  // Ensure that linting occurs before browserify runs. This prevents
  // the build from breaking due to poorly formatted code.
  runSequence(['lint-src', 'lint-test'], () => {
    _browserifyBundle().on('end', () => {
      $.livereload.listen({port: 35729, host: 'localhost', start: true});
      gulp.watch(otherWatchFiles, ['lint-src', 'lint-test']);
    });
  });
}

// Remove the built files
gulp.task('clean', cleanDist);

// Remove our temporary files
gulp.task('clean-tmp', cleanTmp);

// Lint our source code
gulp.task('lint-src', lintSrc);

// Lint our test code
gulp.task('lint-test', lintTest);

// Build two versions of the library
gulp.task('build', ['lint-src', 'clean'], build);

// Lint and run our tests
gulp.task('test', ['lint-src', 'lint-test'], test);

// Set up coverage and run tests
gulp.task('coverage', ['lint-src', 'lint-test'], coverage);

// Set up a livereload environment for our spec runner `test/runner.html`
gulp.task('test-browser', testBrowser);

// Run the headless unit tests as you make changes.
gulp.task('watch', watch);

// An alias of test
gulp.task('default', ['test']);
<|endoftext|>


<commit_before>goog.provide('treesaver.template');

goog.require('treesaver.array');
goog.require('treesaver.dom');
goog.require('treesaver.object');
goog.require('treesaver.string');

/**
 * Expand a class-based template using the given view and
 * class to attribute mappings.
 *
 * @param {!Object} view The object to expand the template with.
 * @param {!Object} mappings An object defining which class
 * names map to attributes. For example: { url: 'href' } would
 * map the 'url' class to the href attribute.
 * @param {?Element} scope The element to use as root for template
 * expansion. Defaults to document if not specified explicitly.
 */
treesaver.template.expand = function(view, mappings, scope) {
  var buffer = [];
  if (!scope) {
    scope = document;
  }
  treesaver.template.expandObject_(view, mappings, scope);
};

/**
 * Helper for template expansion.
 *
 * @private
 * @param {!Object} view The object to expand the template with.
 * @param {!Object} mappings An object defining which class
 * names map to attributes. For example: { url: 'href' } would
 * map the 'url' class to the href attribute.
 * @param {!Element|!HTMLDocument} scope The element to use as root for template
 * expansion.
 */
treesaver.template.expandObject_ = function(view, mappings, scope) {
  var keys = Object.keys(view),
      matches = {};

  keys.forEach(function(key) {
    var elements = [], text;

    if (matches[key]) {
      return;
    }

    elements = treesaver.dom.getElementsByClassName(key, scope);

    if (treesaver.dom.hasClass(scope, key)) {
      elements.push(scope);
    }

    elements.forEach(function(e) {
      if (Array.isArray(view[key])) {
        if (!mappings[key]) {
          elements = treesaver.dom.getElementsByClassName(key, scope);

          elements.forEach(function(e) {
            var template, parent;

            // Find the first element that has a template attribute
            treesaver.array.toArray(e.childNodes).every(function(node) {
              if (treesaver.dom.hasClass(node, 'template')) {
                template = node;
                return false;
              }
            });

            parent = template.parentNode;

            view[key].forEach(function(item) {
              var node = template.cloneNode(true);
              treesaver.template.expandObject_(item, mappings, node);
              parent.appendChild(node);
            });

            parent.removeChild(template);
          });
        } else {
          treesaver.debug.error(
            'treesaver.template: You can not map an array to an attribute.'
          );
        }
      } else if (Object.isObject(view[key])) {
        if (!mappings[key]) {
          elements = treesaver.dom.getElementsByClassName(key, scope);

          // TODO: remove duplicates and non top-level children
          elements.forEach(function(e) {
            treesaver.template.expandObject_(view[key], mappings, e);
          });
        } else {
          treesaver.debug.error(
            'treesaver.template: You can not map an object to an attribute.'
          );
        }
      } else {
        // If we have a custom mapping from class name to attribute
        if (mappings[key]) {
          text = (treesaver.template.escapeURL_[mappings[key]] ? decodeURI(e.getAttribute('data-' + mappings[key])) : e.getAttribute(mappings[key]));
        } else {
          text = e.innerHTML;
        }

        // Are we dealing with an inline template or can we simply
        // replace the whole innerHTML.
        if (treesaver.template.regex_.test(text)) {
          text = text.replace(treesaver.template.regex_, function() {
            var templateKey = arguments[1].toLowerCase().trim();
            if (view[templateKey]) {
              matches[templateKey] = true;
              return treesaver.template.escape_(
                view[templateKey],
                false,
                mappings[key]
              );
            } else {
              matches[templateKey] = false;
              return '';
            }
          });
        } else {
          text = treesaver.template.escape_(view[key], true, mappings[key]);
        }

        if (mappings[key]) {
          e.setAttribute(mappings[key], text);
        } else {
          e.innerHTML = text;
        }
      }
    });
  });

  if (goog.DEBUG) {
    Object.keys(matches).forEach(function(key) {
      if (!matches[key]) {
        treesaver.debug.warn(
          'treesaver.template: Did not match "' + key + '".'
        );
      }
    });
  }
};

/**
 * Escapes a string for use in attributes, URLs or innerHTML.
 *
 * @private
 * @param {string} str The string to escape.
 * @param {!boolean} subpart Whether the string is substring of another
 * string.
 * @param {?string} attributeName Optional attribute name to look up in
 * the treesaver.template.escapeURL object.
 * @return {!string} The escaped string.
 */
treesaver.template.escape_ = function(str, subpart, attributeName) {
  if (!str) {
    return '';
  }

  str = str.toString();

  if (!subpart && attributeName &&
        treesaver.template.escapeURL_[attributeName]) {
    return encodeURIComponent(str);
  } else if (attributeName) {
    // Since we're using setAttribute we don't need to do any custom escaping.
    return str;
  }

  return str.replace(/&(?!\w+;)|[<>]/g, function(s) {
    switch (s) {
      case '&': return '&';
      case '<': return '<';
      case '>': return '>';
      default: return s;
    }
  });
};

/**
 * Regular expression to match template substitutions.
 * @private
 * @type {!RegExp}
 */
treesaver.template.regex_ = new RegExp('{{([^}]+)}}', 'g');

/**
 * A lookup table for attributes that need to be escaped as URL,
 * or as URL components.
 * @private
 * @type {!Object}
 */
treesaver.template.escapeURL_ = {
  href: true,
  src: true
};
<commit_msg>Removed obsolete variable.<commit_after>goog.provide('treesaver.template');

goog.require('treesaver.array');
goog.require('treesaver.dom');
goog.require('treesaver.object');
goog.require('treesaver.string');

/**
 * Expand a class-based template using the given view and
 * class to attribute mappings.
 *
 * @param {!Object} view The object to expand the template with.
 * @param {!Object} mappings An object defining which class
 * names map to attributes. For example: { url: 'href' } would
 * map the 'url' class to the href attribute.
 * @param {?Element} scope The element to use as root for template
 * expansion. Defaults to document if not specified explicitly.
 */
treesaver.template.expand = function(view, mappings, scope) {
  if (!scope) {
    scope = document;
  }
  treesaver.template.expandObject_(view, mappings, scope);
};

/**
 * Helper for template expansion.
 *
 * @private
 * @param {!Object} view The object to expand the template with.
 * @param {!Object} mappings An object defining which class
 * names map to attributes. For example: { url: 'href' } would
 * map the 'url' class to the href attribute.
 * @param {!Element|!HTMLDocument} scope The element to use as root for template
 * expansion.
 */
treesaver.template.expandObject_ = function(view, mappings, scope) {
  var keys = Object.keys(view),
      matches = {};

  keys.forEach(function(key) {
    var elements = [], text;

    if (matches[key]) {
      return;
    }

    elements = treesaver.dom.getElementsByClassName(key, scope);

    if (treesaver.dom.hasClass(scope, key)) {
      elements.push(scope);
    }

    elements.forEach(function(e) {
      if (Array.isArray(view[key])) {
        if (!mappings[key]) {
          elements = treesaver.dom.getElementsByClassName(key, scope);

          elements.forEach(function(e) {
            var template, parent;

            // Find the first element that has a template attribute
            treesaver.array.toArray(e.childNodes).every(function(node) {
              if (treesaver.dom.hasClass(node, 'template')) {
                template = node;
                return false;
              }
            });

            parent = template.parentNode;

            view[key].forEach(function(item) {
              var node = template.cloneNode(true);
              treesaver.template.expandObject_(item, mappings, node);
              parent.appendChild(node);
            });

            parent.removeChild(template);
          });
        } else {
          treesaver.debug.error(
            'treesaver.template: You can not map an array to an attribute.'
          );
        }
      } else if (Object.isObject(view[key])) {
        if (!mappings[key]) {
          elements = treesaver.dom.getElementsByClassName(key, scope);

          // TODO: remove duplicates and non top-level children
          elements.forEach(function(e) {
            treesaver.template.expandObject_(view[key], mappings, e);
          });
        } else {
          treesaver.debug.error(
            'treesaver.template: You can not map an object to an attribute.'
          );
        }
      } else {
        // If we have a custom mapping from class name to attribute
        if (mappings[key]) {
          text = (treesaver.template.escapeURL_[mappings[key]] ? decodeURI(e.getAttribute('data-' + mappings[key])) : e.getAttribute(mappings[key]));
        } else {
          text = e.innerHTML;
        }

        // Are we dealing with an inline template or can we simply
        // replace the whole innerHTML.
        if (treesaver.template.regex_.test(text)) {
          text = text.replace(treesaver.template.regex_, function() {
            var templateKey = arguments[1].toLowerCase().trim();
            if (view[templateKey]) {
              matches[templateKey] = true;
              return treesaver.template.escape_(
                view[templateKey],
                false,
                mappings[key]
              );
            } else {
              matches[templateKey] = false;
              return '';
            }
          });
        } else {
          text = treesaver.template.escape_(view[key], true, mappings[key]);
        }

        if (mappings[key]) {
          e.setAttribute(mappings[key], text);
        } else {
          e.innerHTML = text;
        }
      }
    });
  });

  if (goog.DEBUG) {
    Object.keys(matches).forEach(function(key) {
      if (!matches[key]) {
        treesaver.debug.warn(
          'treesaver.template: Did not match "' + key + '".'
        );
      }
    });
  }
};

/**
 * Escapes a string for use in attributes, URLs or innerHTML.
 *
 * @private
 * @param {string} str The string to escape.
 * @param {!boolean} subpart Whether the string is substring of another
 * string.
 * @param {?string} attributeName Optional attribute name to look up in
 * the treesaver.template.escapeURL object.
 * @return {!string} The escaped string.
 */
treesaver.template.escape_ = function(str, subpart, attributeName) {
  if (!str) {
    return '';
  }

  str = str.toString();

  if (!subpart && attributeName &&
        treesaver.template.escapeURL_[attributeName]) {
    return encodeURIComponent(str);
  } else if (attributeName) {
    // Since we're using setAttribute we don't need to do any custom escaping.
    return str;
  }

  return str.replace(/&(?!\w+;)|[<>]/g, function(s) {
    switch (s) {
      case '&': return '&';
      case '<': return '<';
      case '>': return '>';
      default: return s;
    }
  });
};

/**
 * Regular expression to match template substitutions.
 * @private
 * @type {!RegExp}
 */
treesaver.template.regex_ = new RegExp('{{([^}]+)}}', 'g');

/**
 * A lookup table for attributes that need to be escaped as URL,
 * or as URL components.
 * @private
 * @type {!Object}
 */
treesaver.template.escapeURL_ = {
  href: true,
  src: true
};
<|endoftext|>


<commit_before>
/*
 *  Guacamole - Clientless Remote Desktop
 *  Copyright (C) 2010  Michael Jumper
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Affero General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Affero General Public License for more details.
 *
 *  You should have received a copy of the GNU Affero General Public License
 */

function GuacamoleClient(display, tunnel) {

    var STATE_IDLE          = 0;
    var STATE_CONNECTING    = 1;
    var STATE_WAITING       = 2;
    var STATE_CONNECTED     = 3;
    var STATE_DISCONNECTING = 4;
    var STATE_DISCONNECTED  = 5;

    var currentState = STATE_IDLE;
    var stateChangeHandler = null;

    tunnel.setInstructionHandler(doInstruction);

    // Display must be relatively positioned for mouse to be handled properly
    display.style.position = "relative";

    function setState(state) {
        if (state != currentState) {
            currentState = state;
            if (stateChangeHandler)
                stateChangeHandler(currentState);
        }
    }

    this.setOnStateChangeHandler = function(handler) {
        stateChangeHandler = handler;
    };

    function isConnected() {
        return currentState == STATE_CONNECTED
            || currentState == STATE_WAITING;
    }

    var cursorImage = null;
    var cursorHotspotX = 0;
    var cursorHotspotY = 0;

    // FIXME: Make object. Clean up.
    var cursorRectX = 0;
    var cursorRectY = 0;
    var cursorRectW = 0;
    var cursorRectH = 0;

    var cursorHidden = 0;

    function redrawCursor(x, y) {

        // Hide hardware cursor
        if (cursorHidden == 0) {
            display.className += " guac-hide-cursor";
            cursorHidden = 1;
        }

        // Erase old cursor
        cursor.clearRect(cursorRectX, cursorRectY, cursorRectW, cursorRectH);

        // Update rect
        cursorRectX = x - cursorHotspotX;
        cursorRectY = y - cursorHotspotY;
        cursorRectW = cursorImage.width;
        cursorRectH = cursorImage.height;

        // Draw new cursor
        cursor.drawImage(cursorRectX, cursorRectY, cursorImage);
    }

    this.sendKeyEvent = function(pressed, keysym) {
        // Do not send requests if not connected
        if (!isConnected())
            return;

        tunnel.sendMessage("key:" +  keysym + "," + pressed + ";");
    };

    this.sendMouseState = function(mouseState) {

        // Do not send requests if not connected
        if (!isConnected())
            return;

        // Draw client-side cursor
        if (cursorImage != null) {
            redrawCursor(
                mouseState.getX(),
                mouseState.getY()
            );
        }

        // Build mask
        var buttonMask = 0;
        if (mouseState.getLeft())   buttonMask |= 1;
        if (mouseState.getMiddle()) buttonMask |= 2;
        if (mouseState.getRight())  buttonMask |= 4;
        if (mouseState.getUp())     buttonMask |= 8;
        if (mouseState.getDown())   buttonMask |= 16;

        // Send message
        tunnel.sendMessage("mouse:" + mouseState.getX() + "," + mouseState.getY() + "," + buttonMask + ";");
    };

    this.setClipboard = function(data) {

        // Do not send requests if not connected
        if (!isConnected())
            return;

        tunnel.sendMessage("clipboard:" + escapeGuacamoleString(data) + ";");
    };

    // Handlers

    var nameHandler = null;
    this.setNameHandler = function(handler) {
        nameHandler = handler;
    }

    var errorHandler = null;
    this.setErrorHandler = function(handler) {
        errorHandler = handler;
    };

    var clipboardHandler = null;
    this.setClipboardHandler = function(handler) {
        clipboardHandler = handler;
    };

    // Layers
    var displayWidth = 0;
    var displayHeight = 0;

    var layers = new Array();
    var buffers = new Array();
    var cursor = null;

    this.getLayers = function() {
        return layers;
    };

    function getLayer(index) {

        // If negative index, use buffer
        if (index < 0) {

            index = -1 - index;
            var buffer = buffers[index];

            // Create buffer if necessary
            if (buffer == null) {
                buffer = new Layer(0, 0);
                buffer.setAutosize(1);
                buffers[index] = buffer;
            }

            return buffer;
        }

        // If non-negative, use visible layer
        else {

            var layer = layers[index];
            if (layer == null) {

                // Add new layer
                layer = new Layer(displayWidth, displayHeight);
                layers[index] = layer;

                // (Re)-add existing layers in order
                for (var i=0; i<layers.length; i++) {
                    if (layers[i]) {

                        // If already present, remove
                        if (layers[i].parentNode === display)
                            display.removeChild(layers[i]);

                        // Add to end
                        display.appendChild(layers[i]);
                    }
                }

                // Add cursor layer last
                if (cursor != null) {
                    if (cursor.parentNode === display)
                        display.removeChild(cursor);
                    display.appendChild(cursor);
                }

            }
            else {
                // Reset size
                layer.resize(displayWidth, displayHeight);
            }

            return layer;
        }

    }

    var instructionHandlers = {

        "error": function(parameters) {
            if (errorHandler) errorHandler(unescapeGuacamoleString(parameters[0]));
            disconnect();
        },

        "name": function(parameters) {
            if (nameHandler) nameHandler(unescapeGuacamoleString(parameters[0]));
        },

        "clipboard": function(parameters) {
            if (clipboardHandler) clipboardHandler(unescapeGuacamoleString(parameters[0]));
        },

        "size": function(parameters) {

            displayWidth = parseInt(parameters[0]);
            displayHeight = parseInt(parameters[1]);

            // Update (set) display size
            display.style.width = displayWidth + "px";
            display.style.height = displayHeight + "px";

            // Set cursor layer width/height
            if (cursor != null)
                cursor.resize(displayWidth, displayHeight);

        },

        "png": function(parameters) {

            var layer = parseInt(parameters[0]);
            var x = parseInt(parameters[1]);
            var y = parseInt(parameters[2]);
            var data = parameters[3];

            getLayer(layer).draw(
                x,
                y,
                "data:image/png;base64," + data
            );

            // If received first update, no longer waiting.
            if (currentState == STATE_WAITING)
                setState(STATE_CONNECTED);

        },

        "copy": function(parameters) {

            var srcL = parseInt(parameters[0]);
            var srcX = parseInt(parameters[1]);
            var srcY = parseInt(parameters[2]);
            var srcWidth = parseInt(parameters[3]);
            var srcHeight = parseInt(parameters[4]);
            var dstL = parseInt(parameters[5]);
            var dstX = parseInt(parameters[6]);
            var dstY = parseInt(parameters[7]);

            getLayer(dstL).copyRect(
                getLayer(srcL),
                srcX,
                srcY,
                srcWidth,
                srcHeight,
                dstX,
                dstY
            );

        },

        "cursor": function(parameters) {

            var x = parseInt(parameters[0]);
            var y = parseInt(parameters[1]);
            var data = parameters[2];

            if (cursor == null) {
                cursor = new Layer(displayWidth, displayHeight);
                display.appendChild(cursor);
            }

            // Start cursor image load
            var image = new Image();
            image.onload = function() {
                cursorImage = image;
                cursorHotspotX = x;
                cursorHotspotY = y;
                redrawCursor(cursorRectX, cursorRectY);
            };
            image.src = "data:image/png;base64," + data

        }

    };

    function doInstruction(opcode, parameters) {

        var handler = instructionHandlers[opcode];
        if (handler)
            handler(parameters);

    }

    function disconnect() {

        // Only attempt disconnection not disconnected.
        if (currentState != STATE_DISCONNECTED
                && currentState != STATE_DISCONNECTING) {

            setState(STATE_DISCONNECTING);
            tunnel.sendMessage("disconnect;");
            tunnel.disconnect();
            setState(STATE_DISCONNECTED);
        }

    }

    function escapeGuacamoleString(str) {

        var escapedString = "";

        for (var i=0; i<str.length; i++) {

            var c = str.charAt(i);
            if (c == ",")
                escapedString += "\\c";
            else if (c == ";")
                escapedString += "\\s";
            else if (c == "\\")
                escapedString += "\\\\";
            else
                escapedString += c;

        }

        return escapedString;

    }

    function unescapeGuacamoleString(str) {

        var unescapedString = "";

        for (var i=0; i<str.length; i++) {

            var c = str.charAt(i);
            if (c == "\\" && i<str.length-1) {

                var escapeChar = str.charAt(++i);
                if (escapeChar == "c")
                    unescapedString += ",";
                else if (escapeChar == "s")
                    unescapedString += ";";
                else if (escapeChar == "\\")
                    unescapedString += "\\";
                else
                    unescapedString += "\\" + escapeChar;

            }
            else
                unescapedString += c;

        }

        return unescapedString;

    }

    this.disconnect = disconnect;
    this.connect = function() {

        setState(STATE_CONNECTING);
        tunnel.connect();
        setState(STATE_WAITING);

    };

    this.escapeGuacamoleString   = escapeGuacamoleString;
    this.unescapeGuacamoleString = unescapeGuacamoleString;

}
<commit_msg>Support for sync instruction<commit_after>
/*
 *  Guacamole - Clientless Remote Desktop
 *  Copyright (C) 2010  Michael Jumper
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Affero General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Affero General Public License for more details.
 *
 *  You should have received a copy of the GNU Affero General Public License
 */

function GuacamoleClient(display, tunnel) {

    var STATE_IDLE          = 0;
    var STATE_CONNECTING    = 1;
    var STATE_WAITING       = 2;
    var STATE_CONNECTED     = 3;
    var STATE_DISCONNECTING = 4;
    var STATE_DISCONNECTED  = 5;

    var currentState = STATE_IDLE;
    var stateChangeHandler = null;

    tunnel.setInstructionHandler(doInstruction);

    // Display must be relatively positioned for mouse to be handled properly
    display.style.position = "relative";

    function setState(state) {
        if (state != currentState) {
            currentState = state;
            if (stateChangeHandler)
                stateChangeHandler(currentState);
        }
    }

    this.setOnStateChangeHandler = function(handler) {
        stateChangeHandler = handler;
    };

    function isConnected() {
        return currentState == STATE_CONNECTED
            || currentState == STATE_WAITING;
    }

    var cursorImage = null;
    var cursorHotspotX = 0;
    var cursorHotspotY = 0;

    // FIXME: Make object. Clean up.
    var cursorRectX = 0;
    var cursorRectY = 0;
    var cursorRectW = 0;
    var cursorRectH = 0;

    var cursorHidden = 0;

    function redrawCursor(x, y) {

        // Hide hardware cursor
        if (cursorHidden == 0) {
            display.className += " guac-hide-cursor";
            cursorHidden = 1;
        }

        // Erase old cursor
        cursor.clearRect(cursorRectX, cursorRectY, cursorRectW, cursorRectH);

        // Update rect
        cursorRectX = x - cursorHotspotX;
        cursorRectY = y - cursorHotspotY;
        cursorRectW = cursorImage.width;
        cursorRectH = cursorImage.height;

        // Draw new cursor
        cursor.drawImage(cursorRectX, cursorRectY, cursorImage);
    }

    this.sendKeyEvent = function(pressed, keysym) {
        // Do not send requests if not connected
        if (!isConnected())
            return;

        tunnel.sendMessage("key:" +  keysym + "," + pressed + ";");
    };

    this.sendMouseState = function(mouseState) {

        // Do not send requests if not connected
        if (!isConnected())
            return;

        // Draw client-side cursor
        if (cursorImage != null) {
            redrawCursor(
                mouseState.getX(),
                mouseState.getY()
            );
        }

        // Build mask
        var buttonMask = 0;
        if (mouseState.getLeft())   buttonMask |= 1;
        if (mouseState.getMiddle()) buttonMask |= 2;
        if (mouseState.getRight())  buttonMask |= 4;
        if (mouseState.getUp())     buttonMask |= 8;
        if (mouseState.getDown())   buttonMask |= 16;

        // Send message
        tunnel.sendMessage("mouse:" + mouseState.getX() + "," + mouseState.getY() + "," + buttonMask + ";");
    };

    this.setClipboard = function(data) {

        // Do not send requests if not connected
        if (!isConnected())
            return;

        tunnel.sendMessage("clipboard:" + escapeGuacamoleString(data) + ";");
    };

    // Handlers

    var nameHandler = null;
    this.setNameHandler = function(handler) {
        nameHandler = handler;
    }

    var errorHandler = null;
    this.setErrorHandler = function(handler) {
        errorHandler = handler;
    };

    var clipboardHandler = null;
    this.setClipboardHandler = function(handler) {
        clipboardHandler = handler;
    };

    // Layers
    var displayWidth = 0;
    var displayHeight = 0;

    var layers = new Array();
    var buffers = new Array();
    var cursor = null;

    this.getLayers = function() {
        return layers;
    };

    function getLayer(index) {

        // If negative index, use buffer
        if (index < 0) {

            index = -1 - index;
            var buffer = buffers[index];

            // Create buffer if necessary
            if (buffer == null) {
                buffer = new Layer(0, 0);
                buffer.setAutosize(1);
                buffers[index] = buffer;
            }

            return buffer;
        }

        // If non-negative, use visible layer
        else {

            var layer = layers[index];
            if (layer == null) {

                // Add new layer
                layer = new Layer(displayWidth, displayHeight);
                layers[index] = layer;

                // (Re)-add existing layers in order
                for (var i=0; i<layers.length; i++) {
                    if (layers[i]) {

                        // If already present, remove
                        if (layers[i].parentNode === display)
                            display.removeChild(layers[i]);

                        // Add to end
                        display.appendChild(layers[i]);
                    }
                }

                // Add cursor layer last
                if (cursor != null) {
                    if (cursor.parentNode === display)
                        display.removeChild(cursor);
                    display.appendChild(cursor);
                }

            }
            else {
                // Reset size
                layer.resize(displayWidth, displayHeight);
            }

            return layer;
        }

    }

    var instructionHandlers = {

        "error": function(parameters) {
            if (errorHandler) errorHandler(unescapeGuacamoleString(parameters[0]));
            disconnect();
        },

        "name": function(parameters) {
            if (nameHandler) nameHandler(unescapeGuacamoleString(parameters[0]));
        },

        "clipboard": function(parameters) {
            if (clipboardHandler) clipboardHandler(unescapeGuacamoleString(parameters[0]));
        },

        "size": function(parameters) {

            displayWidth = parseInt(parameters[0]);
            displayHeight = parseInt(parameters[1]);

            // Update (set) display size
            display.style.width = displayWidth + "px";
            display.style.height = displayHeight + "px";

            // Set cursor layer width/height
            if (cursor != null)
                cursor.resize(displayWidth, displayHeight);

        },

        "png": function(parameters) {

            var layer = parseInt(parameters[0]);
            var x = parseInt(parameters[1]);
            var y = parseInt(parameters[2]);
            var data = parameters[3];

            getLayer(layer).draw(
                x,
                y,
                "data:image/png;base64," + data
            );

            // If received first update, no longer waiting.
            if (currentState == STATE_WAITING)
                setState(STATE_CONNECTED);

        },

        "copy": function(parameters) {

            var srcL = parseInt(parameters[0]);
            var srcX = parseInt(parameters[1]);
            var srcY = parseInt(parameters[2]);
            var srcWidth = parseInt(parameters[3]);
            var srcHeight = parseInt(parameters[4]);
            var dstL = parseInt(parameters[5]);
            var dstX = parseInt(parameters[6]);
            var dstY = parseInt(parameters[7]);

            getLayer(dstL).copyRect(
                getLayer(srcL),
                srcX,
                srcY,
                srcWidth,
                srcHeight,
                dstX,
                dstY
            );

        },

        "cursor": function(parameters) {

            var x = parseInt(parameters[0]);
            var y = parseInt(parameters[1]);
            var data = parameters[2];

            if (cursor == null) {
                cursor = new Layer(displayWidth, displayHeight);
                display.appendChild(cursor);
            }

            // Start cursor image load
            var image = new Image();
            image.onload = function() {
                cursorImage = image;
                cursorHotspotX = x;
                cursorHotspotY = y;
                redrawCursor(cursorRectX, cursorRectY);
            };
            image.src = "data:image/png;base64," + data

        },

        "sync": function(parameters) {

            var timestamp = parameters[0];

            // When all layers have finished rendering all instructions
            // UP TO THIS POINT IN TIME, send sync response.

            var layersToSync = 0;
            function syncLayer() {

                layersToSync--;

                // Send sync response when layers are finished
                if (layersToSync == 0)
                    tunnel.sendMessage("sync:" + timestamp + ";");

            }

            // Count active layers and install sync tracking hook
            for (var i=0; i<layers.length; i++) {

                var layer = layers[i];
                if (layer) {
                    layersToSync++;
                    layer.sync(syncLayer);
                }

            }

        },

    };

    function doInstruction(opcode, parameters) {

        var handler = instructionHandlers[opcode];
        if (handler)
            handler(parameters);

    }

    function disconnect() {

        // Only attempt disconnection not disconnected.
        if (currentState != STATE_DISCONNECTED
                && currentState != STATE_DISCONNECTING) {

            setState(STATE_DISCONNECTING);
            tunnel.sendMessage("disconnect;");
            tunnel.disconnect();
            setState(STATE_DISCONNECTED);
        }

    }

    function escapeGuacamoleString(str) {

        var escapedString = "";

        for (var i=0; i<str.length; i++) {

            var c = str.charAt(i);
            if (c == ",")
                escapedString += "\\c";
            else if (c == ";")
                escapedString += "\\s";
            else if (c == "\\")
                escapedString += "\\\\";
            else
                escapedString += c;

        }

        return escapedString;

    }

    function unescapeGuacamoleString(str) {

        var unescapedString = "";

        for (var i=0; i<str.length; i++) {

            var c = str.charAt(i);
            if (c == "\\" && i<str.length-1) {

                var escapeChar = str.charAt(++i);
                if (escapeChar == "c")
                    unescapedString += ",";
                else if (escapeChar == "s")
                    unescapedString += ";";
                else if (escapeChar == "\\")
                    unescapedString += "\\";
                else
                    unescapedString += "\\" + escapeChar;

            }
            else
                unescapedString += c;

        }

        return unescapedString;

    }

    this.disconnect = disconnect;
    this.connect = function() {

        setState(STATE_CONNECTING);
        tunnel.connect();
        setState(STATE_WAITING);

    };

    this.escapeGuacamoleString   = escapeGuacamoleString;
    this.unescapeGuacamoleString = unescapeGuacamoleString;

}
<|endoftext|>


<commit_before>/* @flow */

import { info, track, flush as flushLogs } from 'beaver-logger/client';

import { FPTI } from '../constants';

import { hashStr, match } from './util';
import { getSessionID, getSessionState } from './session';

function isCheckpointUnique(name : string) : boolean {
    return getSessionState(state => {
        state.loggedBeacons = state.loggedBeacons || [];

        if (state.loggedBeacons.indexOf(name) === -1) {
            state.loggedBeacons.push(name);
            return true;
        }

        return false;
    });
}

export function getThrottle(name : string, sample : number) : Object {

    let uid = getSessionID();

    let throttle = hashStr(`${ name }_${ uid }`) % 10000;

    let group;

    if (throttle < sample) {
        group = 'test';
    } else if ((sample >= 5000) || ((sample <= throttle) && (throttle < (sample * 2)))) {
        group = 'control';
    } else {
        group = 'throttle';
    }

    let treatment = `${ name }_${ group }`;

    let started = false;
    let forced = false;

    try {
        if (window.localStorage && window.localStorage.getItem(name)) {
            forced = true;
        }
    } catch (err) {
        // pass
    }

    return {

        isEnabled() : boolean {
            return (group === 'test') || forced;
        },

        isDisabled() : boolean {
            return (group !== 'test') && !forced;
        },

        getTreatment() : string {
            return treatment;
        },

        log(checkpointName : string, payload : { [key : string] : ?string } = {}) : Object {

            if (isCheckpointUnique(`${ name }_${ treatment }`)) {
                track({
                    [ FPTI.KEY.STATE ]:           FPTI.STATE.PXP,
                    [ FPTI.KEY.TRANSITION ]:      FPTI.TRANSITION.PXP,
                    [ FPTI.KEY.EXPERIMENT_NAME ]: name,
                    [ FPTI.KEY.TREATMENT_NAME ]:  treatment,
                    ...payload
                });
            }

            let event = `${ name }_${ treatment }_${ checkpointName }`;

            if (isCheckpointUnique(event)) {
                info(event, { ...payload, expuid: uid });
            }

            flushLogs();

            return this;
        },

        logStart(payload : { [key : string] : ?string } = {}) : Object {
            started = true;
            return this.log(`start`, payload);
        },

        logComplete(payload : { [key : string] : ?string }  = {}) : Object {
            if (started) {
                return this.log(`complete`, payload);
            }

            return this;
        }
    };
}

export function getReturnToken() : ?string {

    let token = match(window.location.href, /token=((EC-)?[A-Z0-9]+)/);
    let payer = match(window.location.href, /PayerID=([A-Z0-9]+)/);

    if (token && payer) {
        return token;
    }
}
<commit_msg>Better typing for throttle objects<commit_after>/* @flow */

import { info, track, flush as flushLogs } from 'beaver-logger/client';

import { FPTI } from '../constants';

import { hashStr, match } from './util';
import { getSessionID, getSessionState } from './session';

function isCheckpointUnique(name : string) : boolean {
    return getSessionState(state => {
        state.loggedBeacons = state.loggedBeacons || [];

        if (state.loggedBeacons.indexOf(name) === -1) {
            state.loggedBeacons.push(name);
            return true;
        }

        return false;
    });
}

type Throttle = {
    isEnabled : () => boolean,
    isDisabled : () => boolean,
    getTreatment : () => string,
    log : (string, payload? : { [string] : ?string }) => Throttle,
    logStart : (payload? : { [string] : ?string }) => Throttle,
    logComplete : (payload? : { [string] : ?string }) => Throttle
};

export function getThrottle(name : string, sample : number) : Throttle {

    let uid = getSessionID();

    let throttle = hashStr(`${ name }_${ uid }`) % 10000;

    let group;

    if (throttle < sample) {
        group = 'test';
    } else if ((sample >= 5000) || ((sample <= throttle) && (throttle < (sample * 2)))) {
        group = 'control';
    } else {
        group = 'throttle';
    }

    let treatment = `${ name }_${ group }`;

    let started = false;
    let forced = false;

    try {
        if (window.localStorage && window.localStorage.getItem(name)) {
            forced = true;
        }
    } catch (err) {
        // pass
    }

    return {

        isEnabled() : boolean {
            return (group === 'test') || forced;
        },

        isDisabled() : boolean {
            return (group !== 'test') && !forced;
        },

        getTreatment() : string {
            return treatment;
        },

        log(checkpointName : string, payload? : { [string] : ?string } = {}) : Throttle {

            if (isCheckpointUnique(`${ name }_${ treatment }`)) {
                track({
                    [ FPTI.KEY.STATE ]:           FPTI.STATE.PXP,
                    [ FPTI.KEY.TRANSITION ]:      FPTI.TRANSITION.PXP,
                    [ FPTI.KEY.EXPERIMENT_NAME ]: name,
                    [ FPTI.KEY.TREATMENT_NAME ]:  treatment,
                    ...payload
                });
            }

            let event = `${ name }_${ treatment }_${ checkpointName }`;

            if (isCheckpointUnique(event)) {
                info(event, { ...payload, expuid: uid });
            }

            flushLogs();

            return this;
        },

        logStart(payload? : { [string] : ?string } = {}) : Throttle {
            started = true;
            return this.log(`start`, payload);
        },

        logComplete(payload? : { [string] : ?string } = {}) : Throttle {
            if (started) {
                return this.log(`complete`, payload);
            }

            return this;
        }
    };
}

export function getReturnToken() : ?string {

    let token = match(window.location.href, /token=((EC-)?[A-Z0-9]+)/);
    let payer = match(window.location.href, /PayerID=([A-Z0-9]+)/);

    if (token && payer) {
        return token;
    }
}
<|endoftext|>