<commit_before>(function(mask) {
	if (typeof DEBUG === 'undefined' || DEBUG === false) {
		return;
	}

	var _cache = {},
		_mask_Component = mask.Dom.Component,
		_mask_getHandler = mask.getHandler,
		_mask_render = mask.render,
		_signal_emitIn = (Compo || mask.Compo)
			.signal.emitIn;

	mask.Module.reload = function (path) {
		var filename = mask.Module.resolvePath({ path: path });
		var endpoint = new mask.Module.Endpoint(filename);
		var module = mask.Module.getCache(endpoint);
		if (module == null) {
			return false;
		}
		var compos = Object.keys(module.exports.__handlers__);
		module.state = 0;
		module.defer();
		module.loadModule().then(function(){
			compos.forEach(function (name, index) {
				reload(name, compos.slice(0, index));
			})
		});
		return true;
	};

	function reload(compoName, reloadedCompoNames) {
		var cache = _cache[compoName];

		_cache[compoName] = [];

		if (!cache) {
			console.log('No compos', compoName, _cache);
			window.location.reload();
			return;
		}

		var imax = cache.length,
			i = -1;
		while (++i < imax) {

			var x = cache[i],
				_instance = x.instance;
				_parent = _instance.parent,
				_stateTree = null;

			if (_instance == null || !_instance.$) {
				console.error('Mask.Reload - no instance', x);
				continue;
			}

			if (wasReloadedViaParent(_instance, reloadedCompoNames)) {
				_cache[compoName].push(x);
				continue;
			}
			_stateTree = serializeStateTree(_instance);

			var $placeholder = dom_createPlaceholder(_instance);
			compo_remove(_instance);

			var frag = _mask_render(x.node, x.model, x.ctx, $placeholder && $placeholder.container, _parent);
			compo_insert(frag, $placeholder, _parent, _stateTree, _instance);
		}
	}

	function serializeStateTree (compo) {
		return mask.TreeWalker.map(compo, function (x) {
			return {
				compoName: x.compoName,
				state: x.serializeState && (x.serializeState() || {}),
				components: null
			};
		});
	}
	function deserializeStateTree (compo, stateTree) {
		var ctx = {};
		mask.TreeWalker.superpose(compo, stateTree, function (x, stateNode) {
			if (stateNode.state != null && x.deserializeState) {
				x.deserializeState(stateNode.state, ctx, compo);
			}
		});
	}

	function wasReloadedViaParent (compo, names) {
		var parent = compo.parent;
		while(parent != null) {
			if (names.indexOf(parent.compoName) !== -1) {
				return true;
			}
			parent = parent.parent;
		}
		return false;
	}

	/** Reload Helpers > */

	function compo_remove(instance) {
		cache_remove(instance);
		if (instance.remove) {
			instance.remove();
			return;
		}
		instance.$ && instance.$.remove();
	}

	function dom_createPlaceholder(instance) {
		var element = instance.$[0];
		if (element == null) {
			return null;
		}
		var parentNode = element.parentNode;
		if (parentNode == null) {
			return null;
		}
		if (parentNode.lastElementChild === element) {
			return { container: parentNode, anchor: null };
		}

		var anchor = document.createComment('');
		parentNode.insertBefore(anchor, element);
		return { container: null, anchor: anchor };
	}

	function compo_insert(fragment, placeholder, parentController, stateTree, prevInstance) {
		if (placeholder && placeholder.anchor) {
			placeholder.parentNode.insertBefore(fragment, placeholder);
		}

		var last = parentController.components[parentController.components.length - 1];
		if (last) {
			_signal_emitIn(last, 'domInsert');
			if (stateTree) {
				deserializeStateTree(last, stateTree);
			}
		}
		for(var x = parentController; x != null; x = x.parent) {
			var compos = x.compos;
			if (compos == null) {
				continue;
			}
			for (var key in compos) {
				if (compos[key] === prevInstance) {
					compos[key] = last;
				}
			}
		}
	}

	function cache_remove (compo) {
		var arr = _cache[compo.compoName];
		var i = arr.indexOf(compo);
		arr.splice(i, 1);
	}

	/* < Reload Helpers */

	mask.on('compoCreated', function(custom, model, ctx, container, node) {
		(_cache[custom.compoName] || (_cache[custom.compoName] = []))
			.push({
				node: node,
				nodes: custom.nodes,
				attr: custom.attr,
				model: model,
				instance: custom,
				ctx: ctx
			});

		var dispose = custom.dispose;
		custom.dispose = function () {
			cache_remove(custom);
			if (dispose) {
				dispose.apply(custom, arguments);
			}
		};
	});

	mask.delegateReload = function() {
		var compos = arguments,
			length = arguments.length;

		return function(source) {
			eval(source);
			for (var i = 0; i < length; i++) {
				reload(compos[i]);
			}
		};
	};

	var _mask_registerHandler = mask.registerHandler,
		_reloadersCache = {};

	mask.registerHandler = function(compoName, handler) {
		_mask_registerHandler(compoName, handler);

		var url = include.url,
			reloader = _reloadersCache[url];

		if (reloader && include.reload && include.reload != reloader) {
			// resource already has reloader, and this is custom
			console.log(' - custom reloader registered. Mask compo reload dropped');
			return;
		}

		if (!reloader) {
			reloader = _reloadersCache[url] = Reloader(compoName);
		} else {
			reloader.compos.push(compoName);
		}

		include.reload = reloader;
	}

	function Reloader(compoName, url) {

		var compos = [compoName],
			reloader = function(source) {
				var length = compos.length;

				eval(source);
				for (var i = 0; i < length; i++) {
					reload(compos[i]);
				}
			};

		reloader.compos = compos;

		return reloader;
	}

}(mask));<commit_msg>fix (autoreload) accessor typo<commit_after>(function(mask) {
	if (typeof DEBUG === 'undefined' || DEBUG === false) {
		return;
	}

	var _cache = {},
		_mask_Component = mask.Dom.Component,
		_mask_getHandler = mask.getHandler,
		_mask_render = mask.render,
		_signal_emitIn = (Compo || mask.Compo)
			.signal.emitIn;

	mask.Module.reload = function (path) {
		var filename = mask.Module.resolvePath({ path: path });
		var endpoint = new mask.Module.Endpoint(filename);
		var module = mask.Module.getCache(endpoint);
		if (module == null) {
			return false;
		}
		var compos = Object.keys(module.exports.__handlers__);
		module.state = 0;
		module.defer();
		module.loadModule().then(function(){
			compos.forEach(function (name, index) {
				reload(name, compos.slice(0, index));
			})
		});
		return true;
	};

	function reload(compoName, reloadedCompoNames) {
		var cache = _cache[compoName];

		_cache[compoName] = [];

		if (!cache) {
			console.log('No compos', compoName, _cache);
			window.location.reload();
			return;
		}

		var imax = cache.length,
			i = -1;
		while (++i < imax) {

			var x = cache[i],
				_instance = x.instance;
				_parent = _instance.parent,
				_stateTree = null;

			if (_instance == null || !_instance.$) {
				console.error('Mask.Reload - no instance', x);
				continue;
			}

			if (wasReloadedViaParent(_instance, reloadedCompoNames)) {
				_cache[compoName].push(x);
				continue;
			}
			_stateTree = serializeStateTree(_instance);

			var $placeholder = dom_createPlaceholder(_instance);
			compo_remove(_instance);

			var frag = _mask_render(x.node, x.model, x.ctx, $placeholder && $placeholder.container, _parent);
			compo_insert(frag, $placeholder, _parent, _stateTree, _instance);
		}
	}

	function serializeStateTree (compo) {
		return mask.TreeWalker.map(compo, function (x) {
			return {
				compoName: x.compoName,
				state: x.serializeState && (x.serializeState() || {}),
				components: null
			};
		});
	}
	function deserializeStateTree (compo, stateTree) {
		var ctx = {};
		mask.TreeWalker.superpose(compo, stateTree, function (x, stateNode) {
			if (stateNode.state != null && x.deserializeState) {
				x.deserializeState(stateNode.state, ctx, compo);
			}
		});
	}

	function wasReloadedViaParent (compo, names) {
		var parent = compo.parent;
		while(parent != null) {
			if (names.indexOf(parent.compoName) !== -1) {
				return true;
			}
			parent = parent.parent;
		}
		return false;
	}

	/** Reload Helpers > */

	function compo_remove(instance) {
		cache_remove(instance);
		if (instance.remove) {
			instance.remove();
			return;
		}
		instance.$ && instance.$.remove();
	}

	function dom_createPlaceholder(instance) {
		var element = instance.$[0];
		if (element == null) {
			return null;
		}
		var parentNode = element.parentNode;
		if (parentNode == null) {
			return null;
		}
		if (parentNode.lastElementChild === element) {
			return { container: parentNode, anchor: null };
		}

		var anchor = document.createComment('');
		parentNode.insertBefore(anchor, element);
		return { container: null, anchor: anchor };
	}

	function compo_insert(fragment, placeholder, parentController, stateTree, prevInstance) {
		if (placeholder && placeholder.anchor) {
			placeholder.anchor.parentNode.insertBefore(fragment, placeholder.anchor);
		}

		var last = parentController.components[parentController.components.length - 1];
		if (last) {
			_signal_emitIn(last, 'domInsert');
			if (stateTree) {
				deserializeStateTree(last, stateTree);
			}
		}
		for(var x = parentController; x != null; x = x.parent) {
			var compos = x.compos;
			if (compos == null) {
				continue;
			}
			for (var key in compos) {
				if (compos[key] === prevInstance) {
					compos[key] = last;
				}
			}
		}
	}

	function cache_remove (compo) {
		var arr = _cache[compo.compoName];
		var i = arr.indexOf(compo);
		arr.splice(i, 1);
	}

	/* < Reload Helpers */

	mask.on('compoCreated', function(custom, model, ctx, container, node) {
		(_cache[custom.compoName] || (_cache[custom.compoName] = []))
			.push({
				node: node,
				nodes: custom.nodes,
				attr: custom.attr,
				model: model,
				instance: custom,
				ctx: ctx
			});

		var dispose = custom.dispose;
		custom.dispose = function () {
			cache_remove(custom);
			if (dispose) {
				dispose.apply(custom, arguments);
			}
		};
	});

	mask.delegateReload = function() {
		var compos = arguments,
			length = arguments.length;

		return function(source) {
			eval(source);
			for (var i = 0; i < length; i++) {
				reload(compos[i]);
			}
		};
	};

	var _mask_registerHandler = mask.registerHandler,
		_reloadersCache = {};

	mask.registerHandler = function(compoName, handler) {
		_mask_registerHandler(compoName, handler);

		var url = include.url,
			reloader = _reloadersCache[url];

		if (reloader && include.reload && include.reload != reloader) {
			// resource already has reloader, and this is custom
			console.log(' - custom reloader registered. Mask compo reload dropped');
			return;
		}

		if (!reloader) {
			reloader = _reloadersCache[url] = Reloader(compoName);
		} else {
			reloader.compos.push(compoName);
		}

		include.reload = reloader;
	}

	function Reloader(compoName, url) {

		var compos = [compoName],
			reloader = function(source) {
				var length = compos.length;

				eval(source);
				for (var i = 0; i < length; i++) {
					reload(compos[i]);
				}
			};

		reloader.compos = compos;

		return reloader;
	}

}(mask));<|endoftext|>


<commit_before>define([
    './options',
    'chai',
    'equivalent-xml',
    'jsdiff',
    'underscore',
    'jquery',
    'jquery.jstree',
    'jquery.vellum'
], function (
    options,
    chai,
    EquivalentXml,
    jsdiff,
    _,
    $
) {
    var assert = chai.assert,
        savedForm = null,
        saveCount = 0;

    // monkey-patch chai to use ===/!== instead of ==/!= in assert.equal
    assert.equal = assert.strictEqual;
    assert.notEqual = assert.notStrictEqual;

    function xmlEqual(str1, str2) {
        var xml1 = EquivalentXml.xml(str1),
            xml2 = EquivalentXml.xml(str2);
        return EquivalentXml.isEquivalent(xml1, xml2, {element_order: true});
    }

    function assertXmlEqual(actual, expected, opts) {
        opts = opts || {};
        if (opts.normalize_xmlns) {
            var xmlns = $($.parseXML(expected)).find('data').attr('xmlns');
            actual = actual.replace(/(data[^>]+xmlns=")(.+?)"/,
                                    '$1' + xmlns + '"');
        }
        var result = xmlEqual(actual, expected);
        if (opts.not) {
            result = !result;
        }
        if (!result) {
            actual = cleanForDiff(actual);
            expected = cleanForDiff(expected);
            assertEqual(actual, expected,
                "XML " + (opts.not ? "should not be equivalent" : "mismatch"));
        }
    }

    function assertXmlNotEqual(actual, expected, opts) {
        opts = opts || {};
        opts.not = true;
        assertXmlEqual(actual, expected, opts);
    }

    function assertJSTreeState() {
        function repr(node, level) {
            var i, len, child, items = [];
            if (node.id === "#") {
                level = -1;
            } else {
                items.push(Array(level * 2 + 1).join(" ") + node.text);
            }
            for (i = 0, len = node.children.length; i < len; i++) {
                child = call("jstree", "get_node", node.children[i]);
                items.push(repr(child, level + 1));
            }
            return items.join("\n");
        }
        var expected = Array.prototype.slice.call(arguments).join("\n") + "\n",
            actual = repr(call("jstree", "get_node", "#")) + "\n";
        assertEqual(actual, expected, "Unexpected jstree state");
    }

    function assertTreeState(tree) {
        function repr(node, level) {
            var i, len, child, items = [];
            if (node.isRootNode) {
                level = -1;
            } else {
                items.push(Array(level * 2 + 1).join(" ") + node.value.p.nodeID);
            }
            for (i = 0, len = node.children.length; i < len; i++) {
                child = node.children[i];
                items.push(repr(child, level + 1));
            }
            return items.join("\n");
        }
        var expected = Array.prototype.slice.call(arguments, 1).join("\n") + "\n",
            actual = repr(tree.rootNode, 0) + "\n";
        assertEqual(actual, expected, "Unexpected tree state");
    }

    /**
     * Assert equality, diff actual/expected if unequal
     */
    function assertEqual(actual, expected, message) {
        if (actual !== expected) {
            var patch = jsdiff.createPatch("", actual, expected, "actual", "expected");
            patch = patch.replace(/^Index:/, message || "Not equal:");
            assert(false, colorDiff(patch));
        }
    }

    function cleanForDiff(value) {
        // convert leading tabs to spaces
        value = value.replace(/^\t+/mg, function (match) {
            return match.replace(/\t/g, "  ");
        });
        // add newline at end of file if missing
        if (!value.match(/\n$/)) {
            value = value + "\n";
        }
        return value;
    }

    function colorDiff(patch) {
        if (!window.mochaPhantomJS) {
            // patch colors not supported in browser yet
            return patch;
        }
        var colors = {
                "-": 31, // red
                "+": 32  // green
            };
        function color(str) {
            var code = colors[str[0]] || 0;
            return '\u001b[' + code + 'm' + str + '\u001b[0m';
        }
        return patch.replace(/^.+?$/gm, color);
    }

    function getInput(property) {
        return call("getCurrentMugInput", property);
    }

    function assertInputCount(nameOrInputs, num, name) {
        if (_.isString(nameOrInputs)) {
            name = " for " + (name ? name + " " : "") + nameOrInputs;
            nameOrInputs = getInput(nameOrInputs);
        } else {
            name = name ? " for " + name : "";
        }
        assert.equal(nameOrInputs.length, num, "wrong number of inputs" + name);
    }

    // initialize/reset active vellum instance
    function init(opts) {
        opts = opts || {};
        var vellum_options = $.extend(true, {}, options.options, opts);
        // $.extend merges arrays :(
        if (opts.javaRosa && opts.javaRosa.langs) {
            vellum_options.javaRosa.langs = opts.javaRosa.langs;
        }
        if (opts.plugins) {
            vellum_options.plugins = opts.plugins;
        }
        vellum_options.core = vellum_options.core || {};
        var originalSaveUrl = vellum_options.core.saveUrl || function () {};
        vellum_options.core.saveUrl = function (data) {
            savedForm = data.xform;
            saveCount++;
            originalSaveUrl(data);
        };
        var vellum = $("#vellum"),
            old = vellum.vellum("get");
        if (old) {
            old.destroy();

            // clean up modal dialog artifacts
            $('.fd-dialog-confirm').dialog("destroy");
            $('body > div.ui-dialog, ' +
              'body > div.modal, ' +
              'body > div.modal-backdrop').remove();
        }
        vellum.empty().vellum(vellum_options);
    }

    // call a method on the active instance
    function call () {
        var args = Array.prototype.slice.call(arguments),
            $vellum = $("#vellum");
        return $vellum.vellum.apply($vellum, args);
    }

    // load XML syncronously
    function loadXML(value, options, ignoreParseWarnings) {
        var warnings = [], data = call("getData");
        data.core.parseWarnings = [];
        call("loadXML", value, options || {});
        if (!ignoreParseWarnings) {
            warnings = data.core.parseWarnings;
        } else if (_.isRegExp(ignoreParseWarnings)) {
            warnings = _.filter(data.core.parseWarnings, function (warning) {
                return !ignoreParseWarnings.test(warning);
            });
        }
        assert(!warnings.length, "unexpected parse warnings:\n- " + warnings.join("\n- "));
        delete data.core.parseWarnings;
        return data.core.form; // return the Form object
    }

    function clickQuestion() {
        var mugs = [],
            ufids = _.map(arguments, function (path) {
                var mug = getMug(path);
                if (!(mug && mug.ufid)) {
                    throw new Error("mug not found: " + path);
                }
                mugs.push(mug);
                return mug.ufid;
            });
        call("jstree", "deselect_all", true);
        call("jstree", "select_node", ufids);
        return mugs;
    }

    function selectAll() {
        call("jstree", "select_all");
    }

    /**
     * Get a mug by path (even items!) from the active form
     *
     * @param path - The path of the item. If this is not an absolute path then
     *               '/data/' will be prepended.
     * @returns null if a mug with the given path is not found.
     */
    function getMug(path) {
        if (path.indexOf("/") !== 0) {
            path = "/data/" + path;
        }
        var mug = call("getMugByPath", path);
        if (!mug) {
            // look for item if the parent mug is a select
            // not sure why getMugByPath doesn't do this... should it?
            var elements = path.split("/"),
                parentPath = elements.slice(0, -1).join("/"),
                parent = call("getMugByPath", parentPath);
            if (parent && parent.__className.indexOf("Select") > -1) {
                var children = call("getData").core.form.getChildren(parent),
                    nodeID = elements[elements.length - 1];
                if (children.length === 1 && nodeID === "itemset" &&
                        children[0].options.tagName === "itemset") {
                    return children[0];
                }
                for (var i = 0; i < children.length; i++) {
                    if (children[i].p.nodeID === nodeID) {
                        return children[i];
                    }
                }
            }
        }
        return mug;
    }

    function deleteQuestion () {
        var mugs = _.map(arguments, function (path) {
                var mug = getMug(path);
                assert(mug, "mug not found: " + path);
                return mug;
            });
        call("getData").core.form.removeMugsFromForm(mugs);
        _.each(arguments, function (path) {
            assert(!getMug(path), "mug not removed: " + path);
        });
    }

    /**
     * Query or set save button enabled state
     *
     * @param value - (opitonal) set enabled/disabled (true/false).
     * @returns - true if the save button is enabled, otherwise false.
     */
    function saveButtonEnabled(value) {
        var button = call("getData").core.saveButton;
        if (!_.isUndefined(value)) {
            var state = value ? "save" : "saved";
            button.setStateWhenReady(state);
            assert.equal(button.state, state, "sanity check failed");
        }
        return button.state === "save";
    }

    function expandGroup(mug) {
        call("jstree", "open_node", mug.ufid);
        call("jstree", "redraw_node", mug.ufid, true, false, false);
    }

    function collapseGroup(mug) {
        call("jstree", "close_node", mug.ufid);
        call("jstree", "redraw_node", mug.ufid, true, false, false);
    }

    function getMessages(mug) {
        var messages = [],
            last = null;
        if (_.isString(mug)) {
            var path = mug;
            mug = getMug(path);
            assert(mug, "mug not found: " + path);
        }
        mug.messages.each(function (msg, attr) {
            if (attr !== last) {
                messages.push(attr + ":");
                last = attr;
            }
            messages.push("  - " + msg.message); // + " [" + msg.key + "]");
        });
        return messages.join("\n");
    }

    return {
        options: options,
        init: init,
        call: call,
        loadXML: loadXML,
        saveAndReload: function(callback) {
            call("loadXFormOrError", call("createXML"), callback);
        },
        getMug: getMug,
        getInput: getInput,
        assertEqual: assertEqual,
        assertInputCount: assertInputCount,
        assertXmlEqual: assertXmlEqual,
        assertXmlNotEqual: assertXmlNotEqual,
        assertJSTreeState: assertJSTreeState,
        assertTreeState: assertTreeState,
        xmlines: function(xml) {
            return xml.replace(/>(\s\s+)</g, ">\n$1<");
        },
        addQuestion: function (qType, nodeId, attrs) {
            attrs = attrs || {};
            if (this.prevId) {
                clickQuestion(this.prevId);
            }
            var mug = call('addQuestion', qType);
            if (nodeId) {
                assert(_.isUndefined(attrs.nodeID),
                       "unexpected attribute for " + qType + "[" + nodeId + "]");
                if (mug.p.labelItext) {
                    mug.p.labelItext.set(nodeId);
                }
                // HACK set nodeID after label itext so tree node gets renamed
                mug.p.nodeID = nodeId;
            }
            _.each(attrs, function (val, name) {
                if (!_.isBoolean(val)) {
                    val = String(val);
                }
                mug.p[name] = val;
            });
            return mug;
        },
        clickQuestion: clickQuestion,
        selectAll: selectAll,
        deleteQuestion: deleteQuestion,
        saveButtonEnabled: saveButtonEnabled,
        expandGroup: expandGroup,
        collapseGroup: collapseGroup,
        getMessages: getMessages,
        isTreeNodeValid: function (mug) {
            if (_.isString(mug)) {
                var path = mug;
                mug = getMug(path);
                assert(mug, "mug not found: " + path);
            }
            var $node = $("#vellum").find('#' + mug.ufid + ' > a');
            return $node.children(".fd-tree-valid-alert-icon").length === 0;
        }
    };
});
<commit_msg>Add util.paste for testing convenience<commit_after>define([
    './options',
    'chai',
    'equivalent-xml',
    'jsdiff',
    'underscore',
    'jquery',
    'vellum/tsv',
    'vellum/copy-paste',
    'jquery.jstree',
    'jquery.vellum'
], function (
    options,
    chai,
    EquivalentXml,
    jsdiff,
    _,
    $,
    tsv,
    copypaste
) {
    var assert = chai.assert,
        savedForm = null,
        saveCount = 0,
        PASTE_HEADER = ["Form Builder clip", "version 1"];

    // monkey-patch chai to use ===/!== instead of ==/!= in assert.equal
    assert.equal = assert.strictEqual;
    assert.notEqual = assert.notStrictEqual;

    function xmlEqual(str1, str2) {
        var xml1 = EquivalentXml.xml(str1),
            xml2 = EquivalentXml.xml(str2);
        return EquivalentXml.isEquivalent(xml1, xml2, {element_order: true});
    }

    function assertXmlEqual(actual, expected, opts) {
        opts = opts || {};
        if (opts.normalize_xmlns) {
            var xmlns = $($.parseXML(expected)).find('data').attr('xmlns');
            actual = actual.replace(/(data[^>]+xmlns=")(.+?)"/,
                                    '$1' + xmlns + '"');
        }
        var result = xmlEqual(actual, expected);
        if (opts.not) {
            result = !result;
        }
        if (!result) {
            actual = cleanForDiff(actual);
            expected = cleanForDiff(expected);
            assertEqual(actual, expected,
                "XML " + (opts.not ? "should not be equivalent" : "mismatch"));
        }
    }

    function assertXmlNotEqual(actual, expected, opts) {
        opts = opts || {};
        opts.not = true;
        assertXmlEqual(actual, expected, opts);
    }

    function assertJSTreeState() {
        function repr(node, level) {
            var i, len, child, items = [];
            if (node.id === "#") {
                level = -1;
            } else {
                items.push(Array(level * 2 + 1).join(" ") + node.text);
            }
            for (i = 0, len = node.children.length; i < len; i++) {
                child = call("jstree", "get_node", node.children[i]);
                items.push(repr(child, level + 1));
            }
            return items.join("\n");
        }
        var expected = Array.prototype.slice.call(arguments).join("\n") + "\n",
            actual = repr(call("jstree", "get_node", "#")) + "\n";
        assertEqual(actual, expected, "Unexpected jstree state");
    }

    function assertTreeState(tree) {
        function repr(node, level) {
            var i, len, child, items = [];
            if (node.isRootNode) {
                level = -1;
            } else {
                items.push(Array(level * 2 + 1).join(" ") + node.value.p.nodeID);
            }
            for (i = 0, len = node.children.length; i < len; i++) {
                child = node.children[i];
                items.push(repr(child, level + 1));
            }
            return items.join("\n");
        }
        var expected = Array.prototype.slice.call(arguments, 1).join("\n") + "\n",
            actual = repr(tree.rootNode, 0) + "\n";
        assertEqual(actual, expected, "Unexpected tree state");
    }

    /**
     * Assert equality, diff actual/expected if unequal
     */
    function assertEqual(actual, expected, message) {
        if (actual !== expected) {
            var patch = jsdiff.createPatch("", actual, expected, "actual", "expected");
            patch = patch.replace(/^Index:/, message || "Not equal:");
            assert(false, colorDiff(patch));
        }
    }

    function cleanForDiff(value) {
        // convert leading tabs to spaces
        value = value.replace(/^\t+/mg, function (match) {
            return match.replace(/\t/g, "  ");
        });
        // add newline at end of file if missing
        if (!value.match(/\n$/)) {
            value = value + "\n";
        }
        return value;
    }

    function colorDiff(patch) {
        if (!window.mochaPhantomJS) {
            // patch colors not supported in browser yet
            return patch;
        }
        var colors = {
                "-": 31, // red
                "+": 32  // green
            };
        function color(str) {
            var code = colors[str[0]] || 0;
            return '\u001b[' + code + 'm' + str + '\u001b[0m';
        }
        return patch.replace(/^.+?$/gm, color);
    }

    function getInput(property) {
        return call("getCurrentMugInput", property);
    }

    function assertInputCount(nameOrInputs, num, name) {
        if (_.isString(nameOrInputs)) {
            name = " for " + (name ? name + " " : "") + nameOrInputs;
            nameOrInputs = getInput(nameOrInputs);
        } else {
            name = name ? " for " + name : "";
        }
        assert.equal(nameOrInputs.length, num, "wrong number of inputs" + name);
    }

    // initialize/reset active vellum instance
    function init(opts) {
        opts = opts || {};
        var vellum_options = $.extend(true, {}, options.options, opts);
        // $.extend merges arrays :(
        if (opts.javaRosa && opts.javaRosa.langs) {
            vellum_options.javaRosa.langs = opts.javaRosa.langs;
        }
        if (opts.plugins) {
            vellum_options.plugins = opts.plugins;
        }
        vellum_options.core = vellum_options.core || {};
        var originalSaveUrl = vellum_options.core.saveUrl || function () {};
        vellum_options.core.saveUrl = function (data) {
            savedForm = data.xform;
            saveCount++;
            originalSaveUrl(data);
        };
        var vellum = $("#vellum"),
            old = vellum.vellum("get");
        if (old) {
            old.destroy();

            // clean up modal dialog artifacts
            $('.fd-dialog-confirm').dialog("destroy");
            $('body > div.ui-dialog, ' +
              'body > div.modal, ' +
              'body > div.modal-backdrop').remove();
        }
        vellum.empty().vellum(vellum_options);
    }

    // call a method on the active instance
    function call () {
        var args = Array.prototype.slice.call(arguments),
            $vellum = $("#vellum");
        return $vellum.vellum.apply($vellum, args);
    }

    // load XML syncronously
    function loadXML(value, options, ignoreParseWarnings) {
        var warnings = [], data = call("getData");
        data.core.parseWarnings = [];
        call("loadXML", value, options || {});
        if (!ignoreParseWarnings) {
            warnings = data.core.parseWarnings;
        } else if (_.isRegExp(ignoreParseWarnings)) {
            warnings = _.filter(data.core.parseWarnings, function (warning) {
                return !ignoreParseWarnings.test(warning);
            });
        }
        assert(!warnings.length, "unexpected parse warnings:\n- " + warnings.join("\n- "));
        delete data.core.parseWarnings;
        return data.core.form; // return the Form object
    }

    function paste(rows, errors, print) {
        if (!_.isString(rows)) {
            rows = tsv.tabDelimit([PASTE_HEADER].concat(rows));
        }
        if (print) { window.console.log(rows); } // debugging helper
        assert.deepEqual(copypaste.paste(rows), errors || []);
    }

    function clickQuestion() {
        var mugs = [],
            ufids = _.map(arguments, function (path) {
                var mug = getMug(path);
                if (!(mug && mug.ufid)) {
                    throw new Error("mug not found: " + path);
                }
                mugs.push(mug);
                return mug.ufid;
            });
        call("jstree", "deselect_all", true);
        call("jstree", "select_node", ufids);
        return mugs;
    }

    function selectAll() {
        call("jstree", "select_all");
    }

    /**
     * Get a mug by path (even items!) from the active form
     *
     * @param path - The path of the item. If this is not an absolute path then
     *               '/data/' will be prepended.
     * @returns null if a mug with the given path is not found.
     */
    function getMug(path) {
        if (path.indexOf("/") !== 0) {
            path = "/data/" + path;
        }
        var mug = call("getMugByPath", path);
        if (!mug) {
            // look for item if the parent mug is a select
            // not sure why getMugByPath doesn't do this... should it?
            var elements = path.split("/"),
                parentPath = elements.slice(0, -1).join("/"),
                parent = call("getMugByPath", parentPath);
            if (parent && parent.__className.indexOf("Select") > -1) {
                var children = call("getData").core.form.getChildren(parent),
                    nodeID = elements[elements.length - 1];
                if (children.length === 1 && nodeID === "itemset" &&
                        children[0].options.tagName === "itemset") {
                    return children[0];
                }
                for (var i = 0; i < children.length; i++) {
                    if (children[i].p.nodeID === nodeID) {
                        return children[i];
                    }
                }
            }
        }
        return mug;
    }

    function deleteQuestion () {
        var mugs = _.map(arguments, function (path) {
                var mug = getMug(path);
                assert(mug, "mug not found: " + path);
                return mug;
            });
        call("getData").core.form.removeMugsFromForm(mugs);
        _.each(arguments, function (path) {
            assert(!getMug(path), "mug not removed: " + path);
        });
    }

    /**
     * Query or set save button enabled state
     *
     * @param value - (opitonal) set enabled/disabled (true/false).
     * @returns - true if the save button is enabled, otherwise false.
     */
    function saveButtonEnabled(value) {
        var button = call("getData").core.saveButton;
        if (!_.isUndefined(value)) {
            var state = value ? "save" : "saved";
            button.setStateWhenReady(state);
            assert.equal(button.state, state, "sanity check failed");
        }
        return button.state === "save";
    }

    function expandGroup(mug) {
        call("jstree", "open_node", mug.ufid);
        call("jstree", "redraw_node", mug.ufid, true, false, false);
    }

    function collapseGroup(mug) {
        call("jstree", "close_node", mug.ufid);
        call("jstree", "redraw_node", mug.ufid, true, false, false);
    }

    function getMessages(mug) {
        var messages = [],
            last = null;
        if (_.isString(mug)) {
            var path = mug;
            mug = getMug(path);
            assert(mug, "mug not found: " + path);
        }
        mug.messages.each(function (msg, attr) {
            if (attr !== last) {
                messages.push(attr + ":");
                last = attr;
            }
            messages.push("  - " + msg.message); // + " [" + msg.key + "]");
        });
        return messages.join("\n");
    }

    return {
        options: options,
        init: init,
        call: call,
        loadXML: loadXML,
        saveAndReload: function(callback) {
            call("loadXFormOrError", call("createXML"), callback);
        },
        getMug: getMug,
        getInput: getInput,
        assertEqual: assertEqual,
        assertInputCount: assertInputCount,
        assertXmlEqual: assertXmlEqual,
        assertXmlNotEqual: assertXmlNotEqual,
        assertJSTreeState: assertJSTreeState,
        assertTreeState: assertTreeState,
        xmlines: function(xml) {
            return xml.replace(/>(\s\s+)</g, ">\n$1<");
        },
        addQuestion: function (qType, nodeId, attrs) {
            attrs = attrs || {};
            if (this.prevId) {
                clickQuestion(this.prevId);
            }
            var mug = call('addQuestion', qType);
            if (nodeId) {
                assert(_.isUndefined(attrs.nodeID),
                       "unexpected attribute for " + qType + "[" + nodeId + "]");
                if (mug.p.labelItext) {
                    mug.p.labelItext.set(nodeId);
                }
                // HACK set nodeID after label itext so tree node gets renamed
                mug.p.nodeID = nodeId;
            }
            _.each(attrs, function (val, name) {
                if (!_.isBoolean(val)) {
                    val = String(val);
                }
                mug.p[name] = val;
            });
            return mug;
        },
        paste: paste,
        clickQuestion: clickQuestion,
        selectAll: selectAll,
        deleteQuestion: deleteQuestion,
        saveButtonEnabled: saveButtonEnabled,
        expandGroup: expandGroup,
        collapseGroup: collapseGroup,
        getMessages: getMessages,
        isTreeNodeValid: function (mug) {
            if (_.isString(mug)) {
                var path = mug;
                mug = getMug(path);
                assert(mug, "mug not found: " + path);
            }
            var $node = $("#vellum").find('#' + mug.ufid + ' > a');
            return $node.children(".fd-tree-valid-alert-icon").length === 0;
        }
    };
});
<|endoftext|>


<commit_before>let fs = require('fs-extra');

let tmpDir;
module.exports = {
    init(cb){
        tmpDir = 'tests/tmp';
        fs.emptyDir(tmpDir, (err) => {
            cb(err);
        });
    },

    getTmpDir(){
        return tmpDir;
    }
}<commit_msg>Trying with absolute directory<commit_after>let fs = require('fs-extra');
let path = require('path');

let tmpDir;
module.exports = {
    init(cb){
        tmpDir = path.resolve(__dirname, '../tests/tmp');
        fs.emptyDir(tmpDir, (err) => {
            cb(err);
        });
    },

    getTmpDir(){
        return tmpDir;
    }
}<|endoftext|>


<commit_before><commit_msg>Main class code added<commit_after>"use strict";
(function (root, factory) {
    if (typeof define === 'function' && define.amd) {
        // AMD
        define([], factory);
    } else if (typeof module === 'object' && module.exports) {
        // Node.js
        module.exports = factory();
    } else {
        // Browser
        root.text2num = factory();
  }
}(this, function () {

    var text2num = {};
    text2num.version = '0.0.1';

    //TRANSLATIONS
    var SMALL_NUMBERS = {

        //English:
        'zero': 0,
        'one': 1,
        'two': 2,
        'three': 3,
        'four': 4,
        'five': 5,
        'six': 6,
        'seven': 7,
        'eight': 8,
        'nine': 9,
        'ten': 10,
        'eleven': 11,
        'twelve': 12,
        'thirteen': 13,
        'fourteen': 14,
        'fifteen': 15,
        'sixteen': 16,
        'seventeen': 17,
        'eighteen': 18,
        'nineteen': 19,
        'twenty': 20,
        'thirty': 30,
        'forty': 40,
        'fifty': 50,
        'sixty': 60,
        'seventy': 70,
        'eighty': 80,
        'ninety': 90,

        //Spanish:
        'cero': 0,
        'uno': 1,
        'dos': 2,
        'tres': 3,
        'cuatro': 4,
        'cinco': 5,
        'seis': 6,
        'siete': 7,
        'ocho': 8,
        'nueve': 9,
        'diez': 10,
        'once': 11,
        'doce': 12,
        'trece': 13,
        'catorce': 14,
        'quince': 15,
        'dieciseis': 16,
        'diecisiete': 17,
        'dieciocho': 18,
        'diecinueve': 19,
        'veinte': 20,
        'treinta': 30,
        'cuarenta': 40,
        'cincuenta': 50,
        'sesenta': 60,
        'setenta': 70,
        'ochenta': 80,
        'noventa': 90,

        'cien': 100,
        'ciento': 100,
        'doscientos': 200,
        'trescientos': 300,
        'cuatrocientos': 400,
        'quinientos': 500,
        'seiscientos': 600,
        'setecientos': 700,
        'ochocientos': 800,
        'novecientos': 900

    };

    var MAGNITUDES = {

        //English:
        'thousand':     1000,
        'million':      1000000,
        'billion':      1000000000,
        'trillion':     1000000000000,
        'quadrillion':  1000000000000000,
        'quintillion':  1000000000000000000,
        'sextillion':   1000000000000000000000,
        'septillion':   1000000000000000000000000,
        'octillion':    1000000000000000000000000000,
        'nonillion':    1000000000000000000000000000000,
        'decillion':    1000000000000000000000000000000000,

        //Spanish:
        'mil':     1000,
        'millón':      1000000,
        'billón':      1000000000,
        'trillón':     1000000000000,
        'quadrillion':  1000000000000000,
        'quintillion':  1000000000000000000,
        'sextillion':   1000000000000000000000,
        'septillion':   1000000000000000000000000,
        'octillion':    1000000000000000000000000000,
        'nonillion':    1000000000000000000000000000000,
        'decillion':    1000000000000000000000000000000000,
    };

    text2num.convert = function(text) {

        wordArray = s.toString().split(/[\s-]+/);
        console.log(a);
        result = 0;
        acc = 0;
        wordArray.forEach(function(textNumber){
            var num = SMALL_NUMBERS[textNumber];
            if (num != null) {
                acc = acc + num;
            }
            else if (textNumber == "hundred") {
                acc = acc * 100;
            }
            else {
                var magnitude = MAGNITUDES[textNumber];
                //Only for Spanish as it is not said "uno mil for 1000, it is only mil":
                if (num != null) {
                    if(acc == 0){
                      acc = 1;
                    }
                    result = result + acc * magnitude;
                    acc = 0;
                }

            }

        });
        return acc + result;
    }

    return text2num;
}));

<|endoftext|>


<commit_before>// Represents a place characters can stand.
function Room(game, x, y) {
    var room = this
    room.game = game
    room.x = x
    room.y = y
    room.links = {}
    room.entered = new SprigganEventRecurring()
    room.exited = new SprigganEventRecurring()
    room.arrived = new SprigganEventRecurring()
    room.characters = []
    room.idleCharacters = []
    game.contentManager.add(SprigganSpriteSheet, "rooms/rooms")
    game.contentLoaded.listen(function(){
        room.sprite = new SprigganSprite(game.backgroundGroup, game.contentManager, "rooms/rooms", function(){
            room.game.mode.clicked(room)
        })
        room.sprite.move(room.x * 64, room.y * 64)
        room.sprite.loop("room")
    })
}

Room.prototype.addIdleCharacter = function(character, facing) {
    if (this.idleCharacters.indexOf(character) != -1) return

    // Try and find the "best" place in the list given the characters run
    // counter clockwise from south.
    if (!this.idleCharacters.length) {
        this.idleCharacters.push(character)
    } else if (this.idleCharacters.length == 1) {
        switch(facing) {
            case "up":
                // Start of array, south.
                this.idleCharacters.unshift(character)
                break

            case "down":
                // End of array, north.
                this.idleCharacters.push(character)
                break

            default:
                if (Math.random() >= 0.5)
                    this.idleCharacters.push(character)
                else
                    this.idleCharacters.unshift(character)
                break
        }
    } else {
        switch (facing) {
            case "left":
                this.idleCharacters.splice(Math.ceil(this.idleCharacters.length / 4), 0, character)
                break
            case "right":
                this.idleCharacters.splice(Math.ceil(this.idleCharacters.length * 3 / 4), 0, character)
                break
            case "up":
                this.idleCharacters.unshift(character)
                break
            case "down":
                this.idleCharacters.splice(Math.ceil(this.idleCharacters.length / 2), 0, character)
                break
        }
    }

    for (var i = 0; i < this.idleCharacters.length; i++) this.idleCharacters[i].think()
}

Room.prototype.removeIdleCharacter = function(character) {
    if (this.idleCharacters.indexOf(character) == -1) return

    SprigganRemoveByValue(this.idleCharacters, character)
    for (var i = 0; i < this.idleCharacters.length; i++) this.idleCharacters[i].think()
}

// A Room which changes the room when all party members are inside.
function Door(game, spriteSheetUrl, x, y, roomPath) {
    Room.call(this, game, spriteSheetUrl, x, y)
    this.arrived.listen(function() {
        game.dispose()
        game.savegame.roomPath = roomPath
        new Game(game.savegame)
    })
}

// Given a destination room, returns the direction in which to go to reach it.
// Throws errors when the destination room is inaccessible or this room.
Room.prototype.navigateTo = function(checkIsDestination) {
    if (checkIsDestination(this)) return null

    var best = Infinity
    var bestOption = null

    // Floodfill from each link from this room.
    for (var key in this.links) {
        var checked = [{
            room: this,
            distance: 0
        }]

        function Recurse(fromRoom, link, distance) {
            if (!link.walkable(fromRoom)) return Infinity
            var toRoom = link.roomOpposite(fromRoom)
            if (checkIsDestination(toRoom)) return distance

            var obj, i

            for (i = 0; i < checked.length; i++) {
                obj = checked[i]
                if (obj.room != toRoom) continue
                if (obj.distance <= distance) return Infinity
                obj.distance = distance
                break
            }

            if (i == checked.length) {
                obj = { room: toRoom, distance: distance }
                checked.push(obj)
            }

            var best = Infinity

            for (var key in toRoom.links) {
                var linkDistance = Recurse(toRoom, toRoom.links[key], distance + 1)
                if (linkDistance < best) best = linkDistance
            }

            return best
        }

        var dist = Recurse(this, this.links[key], 1)
        if (dist >= best) continue
        best = dist
        bestOption = key
    }

    return bestOption
}

Room.prototype.emitLineOfSight = function(distance, waistHigh, callback) {
    var room = this
    callback(room)

    for (var key in room.links) {
        var fromRoom = room
        var link = room.links[key]
        var travelled = 0
        while (link) {
            if (travelled++ > distance) break
            if (link.blocksLineOfSight(fromRoom)) break
            if (waistHigh && link.blocksLineOfSightBelowWaist(fromRoom)) break
            var nextRoom = link.roomOpposite(fromRoom)
            callback(nextRoom)
            link = nextRoom.links[key]
            fromRoom = nextRoom
        }
    }
}

Room.prototype.getDirectionToRoom = function (toRoom) {
    if (this.x == toRoom.x) {
        if (this.y > toRoom.y)
            return "up"
        else if (this.y < toRoom.y)
            return "down"
    } else if (this.y == toRoom.y) {
        if (this.x > toRoom.x)
            return "left"
        else
            return "right"
    }

    return null
}

Room.prototype.hasLineOfSightToRoom = function (toRoom, waistHigh) {
    if (this == toRoom) return true
    var direction = this.getDirectionToRoom(toRoom)
    if (!direction) return false

    var fromRoom = this
    while (fromRoom != toRoom) {
        var link = fromRoom.links[direction]
        if (!link) return false
        if (link.blocksLineOfSight(fromRoom)) return false
        if (waistHigh && link.blocksLineOfSightBelowWaist(fromRoom)) return false
        fromRoom = link.roomOpposite(fromRoom)
    }

    return true
}

function MakeLink(type) {
    type.prototype.roomOpposite = function(room) {
        return room == this.fromRoom ? this.toRoom : this.fromRoom
    }

    type.prototype.enteredBy = function(character) {}
    type.prototype.leftBy = function(character) {}

    type.prototype.blocksLineOfSight = function(fromRoom) {
        return false
    }

    type.prototype.blocksLineOfSightBelowWaist = function(fromRoom) {
        return false
    }

    type.prototype.linkToRooms = function() {
        if (this.fromRoom.y == this.toRoom.y) {
            this.orientation = "horizontal"
            if (this.fromRoom.x > this.toRoom.x) {
                this.direction = "left"
                this.fromRoom.links.left = this
                this.toRoom.links.right = this
            } else {
                this.direction = "right"
                this.fromRoom.links.right = this
                this.toRoom.links.left = this
            }
        } else {
            this.orientation = "vertical"
            if (this.fromRoom.y > this.toRoom.y) {
                this.direction = "up"
                this.fromRoom.links.up = this
                this.toRoom.links.down = this
            } else {
                this.direction = "down"
                this.fromRoom.links.down = this
                this.toRoom.links.up = this
            }
            return "Horizontal"
        }
    }
}

function Door(fromRoom, toRoom) {
    var door = this
    door.users = 0
    door.fromRoom = fromRoom
    door.toRoom = toRoom
    door.linkToRooms()

    door.animationPrefix = "door" + Capitalize(door.orientation)

    door.game = toRoom.game
    door.game.contentManager.add(SprigganSpriteSheet, "rooms/rooms")

    door.game.contentLoaded.listen(function() {
        door.sprite = new SprigganSprite(door.game.backgroundOverlayGroup, door.game.contentManager, "rooms/rooms")
        door.sprite.move((fromRoom.x + toRoom.x) * 32, (fromRoom.y + toRoom.y) * 32)
        door.sprite.loop(door.animationPrefix + "Closed")

        door.foregroundSprite = new SprigganSprite(door.game.foregroundGroup, door.game.contentManager, "rooms/rooms")
        door.foregroundSprite.move((fromRoom.x + toRoom.x) * 32, (fromRoom.y + toRoom.y) * 32)
        door.foregroundSprite.loop(door.animationPrefix + "Foreground")
    })
}

MakeLink(Door)

Door.prototype.enteredBy = function(character) {
    var door = this
    if (!door.users) door.sprite.play(door.animationPrefix + "Opening", function() {
        door.sprite.loop(door.animationPrefix + "Opened")
    })
    door.users++
}

Door.prototype.leftBy = function(character) {
    var door = this
    door.users--
    if (!door.users) door.sprite.play(door.animationPrefix + "Closing", function() {
        door.sprite.loop(door.animationPrefix + "Closed")
    })
}

Door.prototype.walkable = function(fromRoom) {
    return true
}

Door.prototype.blocksLineOfSight = function(fromRoom) {
    return !this.users
}

function Path(fromRoom, toRoom) {
    var path = this
    path.fromRoom = fromRoom
    path.toRoom = toRoom
    path.linkToRooms()
    path.game = toRoom.game
    path.game.contentManager.add(SprigganSpriteSheet, "rooms/rooms")
    path.game.contentLoaded.listen(function() {
        path.sprite = new SprigganSprite(path.game.backgroundOverlayGroup, path.game.contentManager, "rooms/rooms")
        path.sprite.move((fromRoom.x + toRoom.x) * 32, (fromRoom.y + toRoom.y) * 32)
        path.sprite.loop("path" + Capitalize(path.orientation))
    })
}

MakeLink(Path)

Path.prototype.walkable = function(fromRoom) {
    return true
}

function Ledge(fromRoom, toRoom) {
    var ledge = this
    ledge.fromRoom = fromRoom
    ledge.toRoom = toRoom
    ledge.linkToRooms()
    ledge.game = toRoom.game
    ledge.game.contentManager.add(SprigganSpriteSheet, "rooms/rooms")
    ledge.game.contentLoaded.listen(function() {
        ledge.sprite = new SprigganSprite(ledge.game.backgroundOverlayGroup, ledge.game.contentManager, "rooms/rooms")
        ledge.sprite.move((fromRoom.x + toRoom.x) * 32, (fromRoom.y + toRoom.y) * 32)
        ledge.sprite.loop("ledge" + Capitalize(ledge.direction))
    })
}

MakeLink(Ledge)

Ledge.prototype.walkable = function(fromRoom) {
    return fromRoom == this.fromRoom
}

function Window(room, position) {
    var window = this
    window.room = room
    window.game = room.game
    window.game.contentManager.add(SprigganSpriteSheet, "rooms/rooms")
    window.game.contentLoaded.listen(function() {
        window.sprite = new SprigganSprite(window.game.backgroundOverlayGroup, window.game.contentManager, "rooms/rooms")
        window.sprite.move(room.x * 64, room.y * 64)
        window.sprite.loop("window" + position[0].toUpperCase() + position.slice(1))
    })
}<commit_msg>Removed the unused "Door" class.<commit_after>// Represents a place characters can stand.
function Room(game, x, y) {
    var room = this
    room.game = game
    room.x = x
    room.y = y
    room.links = {}
    room.entered = new SprigganEventRecurring()
    room.exited = new SprigganEventRecurring()
    room.arrived = new SprigganEventRecurring()
    room.characters = []
    room.idleCharacters = []
    game.contentManager.add(SprigganSpriteSheet, "rooms/rooms")
    game.contentLoaded.listen(function(){
        room.sprite = new SprigganSprite(game.backgroundGroup, game.contentManager, "rooms/rooms", function(){
            room.game.mode.clicked(room)
        })
        room.sprite.move(room.x * 64, room.y * 64)
        room.sprite.loop("room")
    })
}

Room.prototype.addIdleCharacter = function(character, facing) {
    if (this.idleCharacters.indexOf(character) != -1) return

    // Try and find the "best" place in the list given the characters run
    // counter clockwise from south.
    if (!this.idleCharacters.length) {
        this.idleCharacters.push(character)
    } else if (this.idleCharacters.length == 1) {
        switch(facing) {
            case "up":
                // Start of array, south.
                this.idleCharacters.unshift(character)
                break

            case "down":
                // End of array, north.
                this.idleCharacters.push(character)
                break

            default:
                if (Math.random() >= 0.5)
                    this.idleCharacters.push(character)
                else
                    this.idleCharacters.unshift(character)
                break
        }
    } else {
        switch (facing) {
            case "left":
                this.idleCharacters.splice(Math.ceil(this.idleCharacters.length / 4), 0, character)
                break
            case "right":
                this.idleCharacters.splice(Math.ceil(this.idleCharacters.length * 3 / 4), 0, character)
                break
            case "up":
                this.idleCharacters.unshift(character)
                break
            case "down":
                this.idleCharacters.splice(Math.ceil(this.idleCharacters.length / 2), 0, character)
                break
        }
    }

    for (var i = 0; i < this.idleCharacters.length; i++) this.idleCharacters[i].think()
}

Room.prototype.removeIdleCharacter = function(character) {
    if (this.idleCharacters.indexOf(character) == -1) return

    SprigganRemoveByValue(this.idleCharacters, character)
    for (var i = 0; i < this.idleCharacters.length; i++) this.idleCharacters[i].think()
}

// Given a destination room, returns the direction in which to go to reach it.
// Throws errors when the destination room is inaccessible or this room.
Room.prototype.navigateTo = function(checkIsDestination) {
    if (checkIsDestination(this)) return null

    var best = Infinity
    var bestOption = null

    // Floodfill from each link from this room.
    for (var key in this.links) {
        var checked = [{
            room: this,
            distance: 0
        }]

        function Recurse(fromRoom, link, distance) {
            if (!link.walkable(fromRoom)) return Infinity
            var toRoom = link.roomOpposite(fromRoom)
            if (checkIsDestination(toRoom)) return distance

            var obj, i

            for (i = 0; i < checked.length; i++) {
                obj = checked[i]
                if (obj.room != toRoom) continue
                if (obj.distance <= distance) return Infinity
                obj.distance = distance
                break
            }

            if (i == checked.length) {
                obj = { room: toRoom, distance: distance }
                checked.push(obj)
            }

            var best = Infinity

            for (var key in toRoom.links) {
                var linkDistance = Recurse(toRoom, toRoom.links[key], distance + 1)
                if (linkDistance < best) best = linkDistance
            }

            return best
        }

        var dist = Recurse(this, this.links[key], 1)
        if (dist >= best) continue
        best = dist
        bestOption = key
    }

    return bestOption
}

Room.prototype.emitLineOfSight = function(distance, waistHigh, callback) {
    var room = this
    callback(room)

    for (var key in room.links) {
        var fromRoom = room
        var link = room.links[key]
        var travelled = 0
        while (link) {
            if (travelled++ > distance) break
            if (link.blocksLineOfSight(fromRoom)) break
            if (waistHigh && link.blocksLineOfSightBelowWaist(fromRoom)) break
            var nextRoom = link.roomOpposite(fromRoom)
            callback(nextRoom)
            link = nextRoom.links[key]
            fromRoom = nextRoom
        }
    }
}

Room.prototype.getDirectionToRoom = function (toRoom) {
    if (this.x == toRoom.x) {
        if (this.y > toRoom.y)
            return "up"
        else if (this.y < toRoom.y)
            return "down"
    } else if (this.y == toRoom.y) {
        if (this.x > toRoom.x)
            return "left"
        else
            return "right"
    }

    return null
}

Room.prototype.hasLineOfSightToRoom = function (toRoom, waistHigh) {
    if (this == toRoom) return true
    var direction = this.getDirectionToRoom(toRoom)
    if (!direction) return false

    var fromRoom = this
    while (fromRoom != toRoom) {
        var link = fromRoom.links[direction]
        if (!link) return false
        if (link.blocksLineOfSight(fromRoom)) return false
        if (waistHigh && link.blocksLineOfSightBelowWaist(fromRoom)) return false
        fromRoom = link.roomOpposite(fromRoom)
    }

    return true
}

function MakeLink(type) {
    type.prototype.roomOpposite = function(room) {
        return room == this.fromRoom ? this.toRoom : this.fromRoom
    }

    type.prototype.enteredBy = function(character) {}
    type.prototype.leftBy = function(character) {}

    type.prototype.blocksLineOfSight = function(fromRoom) {
        return false
    }

    type.prototype.blocksLineOfSightBelowWaist = function(fromRoom) {
        return false
    }

    type.prototype.linkToRooms = function() {
        if (this.fromRoom.y == this.toRoom.y) {
            this.orientation = "horizontal"
            if (this.fromRoom.x > this.toRoom.x) {
                this.direction = "left"
                this.fromRoom.links.left = this
                this.toRoom.links.right = this
            } else {
                this.direction = "right"
                this.fromRoom.links.right = this
                this.toRoom.links.left = this
            }
        } else {
            this.orientation = "vertical"
            if (this.fromRoom.y > this.toRoom.y) {
                this.direction = "up"
                this.fromRoom.links.up = this
                this.toRoom.links.down = this
            } else {
                this.direction = "down"
                this.fromRoom.links.down = this
                this.toRoom.links.up = this
            }
            return "Horizontal"
        }
    }
}

function Door(fromRoom, toRoom) {
    var door = this
    door.users = 0
    door.fromRoom = fromRoom
    door.toRoom = toRoom
    door.linkToRooms()

    door.animationPrefix = "door" + Capitalize(door.orientation)

    door.game = toRoom.game
    door.game.contentManager.add(SprigganSpriteSheet, "rooms/rooms")

    door.game.contentLoaded.listen(function() {
        door.sprite = new SprigganSprite(door.game.backgroundOverlayGroup, door.game.contentManager, "rooms/rooms")
        door.sprite.move((fromRoom.x + toRoom.x) * 32, (fromRoom.y + toRoom.y) * 32)
        door.sprite.loop(door.animationPrefix + "Closed")

        door.foregroundSprite = new SprigganSprite(door.game.foregroundGroup, door.game.contentManager, "rooms/rooms")
        door.foregroundSprite.move((fromRoom.x + toRoom.x) * 32, (fromRoom.y + toRoom.y) * 32)
        door.foregroundSprite.loop(door.animationPrefix + "Foreground")
    })
}

MakeLink(Door)

Door.prototype.enteredBy = function(character) {
    var door = this
    if (!door.users) door.sprite.play(door.animationPrefix + "Opening", function() {
        door.sprite.loop(door.animationPrefix + "Opened")
    })
    door.users++
}

Door.prototype.leftBy = function(character) {
    var door = this
    door.users--
    if (!door.users) door.sprite.play(door.animationPrefix + "Closing", function() {
        door.sprite.loop(door.animationPrefix + "Closed")
    })
}

Door.prototype.walkable = function(fromRoom) {
    return true
}

Door.prototype.blocksLineOfSight = function(fromRoom) {
    return !this.users
}

function Path(fromRoom, toRoom) {
    var path = this
    path.fromRoom = fromRoom
    path.toRoom = toRoom
    path.linkToRooms()
    path.game = toRoom.game
    path.game.contentManager.add(SprigganSpriteSheet, "rooms/rooms")
    path.game.contentLoaded.listen(function() {
        path.sprite = new SprigganSprite(path.game.backgroundOverlayGroup, path.game.contentManager, "rooms/rooms")
        path.sprite.move((fromRoom.x + toRoom.x) * 32, (fromRoom.y + toRoom.y) * 32)
        path.sprite.loop("path" + Capitalize(path.orientation))
    })
}

MakeLink(Path)

Path.prototype.walkable = function(fromRoom) {
    return true
}

function Ledge(fromRoom, toRoom) {
    var ledge = this
    ledge.fromRoom = fromRoom
    ledge.toRoom = toRoom
    ledge.linkToRooms()
    ledge.game = toRoom.game
    ledge.game.contentManager.add(SprigganSpriteSheet, "rooms/rooms")
    ledge.game.contentLoaded.listen(function() {
        ledge.sprite = new SprigganSprite(ledge.game.backgroundOverlayGroup, ledge.game.contentManager, "rooms/rooms")
        ledge.sprite.move((fromRoom.x + toRoom.x) * 32, (fromRoom.y + toRoom.y) * 32)
        ledge.sprite.loop("ledge" + Capitalize(ledge.direction))
    })
}

MakeLink(Ledge)

Ledge.prototype.walkable = function(fromRoom) {
    return fromRoom == this.fromRoom
}

function Window(room, position) {
    var window = this
    window.room = room
    window.game = room.game
    window.game.contentManager.add(SprigganSpriteSheet, "rooms/rooms")
    window.game.contentLoaded.listen(function() {
        window.sprite = new SprigganSprite(window.game.backgroundOverlayGroup, window.game.contentManager, "rooms/rooms")
        window.sprite.move(room.x * 64, room.y * 64)
        window.sprite.loop("window" + position[0].toUpperCase() + position.slice(1))
    })
}<|endoftext|>


<commit_before>var through2 = require('through2');
var specialID = require('../../data/specialCases');

function featureCodeToLayerDefault(featureCode) {
  switch (featureCode) {
      case 'PCLI':
          return 'country';
      case 'ADM1':
          return 'region';
      case 'ADM2':
          return 'county';
      case 'ADMD':
          return 'localadmin';
      case 'PPL':
      case 'PPLA':
      case 'PPLA2':
      case 'PPLA3':
      case 'PPLA4':
      case 'PPLC':
      case 'PPLF':
      case 'PPLG':
      case 'PPLL':
      case 'PPLS':
      case 'LCTY':
      case 'STLMT':
          return 'locality';
      case 'ADM4':
      case 'ADM5':
      case 'PPLX':
          return 'neighbourhood';
      default:
          return 'venue';
  }
}

function featureCodeToLayerGB(featureCode) {
  switch (featureCode) {
      case 'ADM1':
          return 'macroregion';
      default:
          return featureCodeToLayerDefault(featureCode);
  }
}

function featureCodeToLayerES(featureCode) {
  switch (featureCode) {
      case 'ADM1':
          return 'macroregion';
      default:
          return featureCodeToLayerDefault(featureCode);
  }
}

function featureCodeToLayerFR(featureCode) {
  switch (featureCode) {
      case 'RGN':
          return 'macroregion';
      default:
          return featureCodeToLayerDefault(featureCode);
  }
}

function featureCodeToLayerIT(featureCode) {
  switch (featureCode) {
      case 'ADM1':
          return 'macroregion';
      default:
          return featureCodeToLayerDefault(featureCode);
  }
}

function featureCodeToLayer(featureCode, countryCode) {
  switch (countryCode) {
      case 'GB':
          return featureCodeToLayerGB(featureCode);
      case 'ES':
          return featureCodeToLayerES(featureCode);
      case 'FR':
          return featureCodeToLayerFR(featureCode);
      case 'IT':
          return featureCodeToLayerIT(featureCode);
      default:
          return featureCodeToLayerDefault(featureCode);
  }
}

function mapIDtoLayer(id){
  return specialID[id];
}

function create() {
  return through2.obj(function(data, enc, next) {
    data.layer = mapIDtoLayer(data._id) || featureCodeToLayer(data.feature_code, data.country_code);

    next(null, data);
  });
}

module.exports = {
  featureCodeToLayer: featureCodeToLayer,
  create: create
};
<commit_msg>fix: Merge pull request #120 from pelias/add-adm3<commit_after>var through2 = require('through2');
var specialID = require('../../data/specialCases');

function featureCodeToLayerDefault(featureCode) {
  switch (featureCode) {
      case 'PCLI':
          return 'country';
      case 'ADM1':
          return 'region';
      case 'ADM2':
          return 'county';
      case 'ADMD':
          return 'localadmin';
      case 'ADM3':
      case 'PPL':
      case 'PPLA':
      case 'PPLA2':
      case 'PPLA3':
      case 'PPLA4':
      case 'PPLC':
      case 'PPLF':
      case 'PPLG':
      case 'PPLL':
      case 'PPLS':
      case 'LCTY':
      case 'STLMT':
          return 'locality';
      case 'ADM4':
      case 'ADM5':
      case 'PPLX':
          return 'neighbourhood';
      default:
          return 'venue';
  }
}

function featureCodeToLayerGB(featureCode) {
  switch (featureCode) {
      case 'ADM1':
          return 'macroregion';
      default:
          return featureCodeToLayerDefault(featureCode);
  }
}

function featureCodeToLayerES(featureCode) {
  switch (featureCode) {
      case 'ADM1':
          return 'macroregion';
      default:
          return featureCodeToLayerDefault(featureCode);
  }
}

function featureCodeToLayerFR(featureCode) {
  switch (featureCode) {
      case 'RGN':
          return 'macroregion';
      default:
          return featureCodeToLayerDefault(featureCode);
  }
}

function featureCodeToLayerIT(featureCode) {
  switch (featureCode) {
      case 'ADM1':
          return 'macroregion';
      default:
          return featureCodeToLayerDefault(featureCode);
  }
}

function featureCodeToLayer(featureCode, countryCode) {
  switch (countryCode) {
      case 'GB':
          return featureCodeToLayerGB(featureCode);
      case 'ES':
          return featureCodeToLayerES(featureCode);
      case 'FR':
          return featureCodeToLayerFR(featureCode);
      case 'IT':
          return featureCodeToLayerIT(featureCode);
      default:
          return featureCodeToLayerDefault(featureCode);
  }
}

function mapIDtoLayer(id){
  return specialID[id];
}

function create() {
  return through2.obj(function(data, enc, next) {
    data.layer = mapIDtoLayer(data._id) || featureCodeToLayer(data.feature_code, data.country_code);

    next(null, data);
  });
}

module.exports = {
  featureCodeToLayer: featureCodeToLayer,
  create: create
};
<|endoftext|>


<commit_before>/*!
 * Copyright (c) 2017-2018 Digital Bazaar, Inc. All rights reserved.
 */
'use strict';

const constants = require('../constants');
const jsonld = require('jsonld');
const util = require('../util');
const {sha256digest} = require('../sha256digest');
const LinkedDataProof = require('./LinkedDataProof');

module.exports = class LinkedDataSignature extends LinkedDataProof {
  /**
   * @param {object} options - Options hashmap.
   * @param {string} options.type - Suite name, provided by subclass.
   * @typedef LDKeyPair
   * @param {LDKeyPair} LDKeyClass - The crypto-ld key class that this suite
   *   will use to sign/verify signatures. Provided by subclass. Used
   *   during the `verifySignature` operation, to create an instance (containing
   *   a `verifier()` property) of a public key fetched via a `documentLoader`.
   *
   * Either a `key` OR at least one of `signer`/`verifier` is required:
   *
   * @param {object} [options.key] - An optional key object (containing an
   *   `id` property, and either `signer` or `verifier`, depending on the
   *   intended operation. Useful for when the application is managing keys
   *   itself (when using a KMS, you never have access to the private key,
   *   and so should use the `signer` param instead).
   * @param {Function} [options.signer] - Signer function that returns an
   *   object with an async sign() method. This is useful when interfacing
   *   with a KMS (since you don't get access to the private key and its
   *   `signer()`, the KMS client gives you only the signer function to use).
   * @param {Function} [options.verifier] - Verifier function that returns
   *   an object with an async `verify()` method. Useful when working with a
   *   KMS-provided verifier function.
   *
   * Advanced optional parameters and overrides:
   *
   * @param {object} [options.proof] - A JSON-LD document with options to use
   *   for the `proof` node (e.g. any other custom fields can be provided here
   *   using a context different from security-v2). If not provided, this is
   *   constructed during signing.
   * @param {string|Date} [options.date] - Signing date to use if not passed.
   * @param {boolean} [options.useNativeCanonize] - Whether to use a native
   *   canonize algorithm.
   */
  constructor({
    type, proof, LDKeyClass, date, key, signer, verifier,
    useNativeCanonize
  } = {}) {
    super({type});
    this.LDKeyClass = LDKeyClass;
    this.proof = proof;
    const vm = this._processSignatureParams({key, signer, verifier});
    this.verificationMethod = vm.verificationMethod;
    this.key = vm.key;
    this.signer = vm.signer;
    this.verifier = vm.verifier;
    if(date) {
      this.date = new Date(date);
      if(isNaN(this.date)) {
        throw TypeError(`"date" "${date}" is not a valid date.`);
      }
    }
    this.useNativeCanonize = useNativeCanonize;
  }

  /**
   * @param document {object} to be signed.
   * @param purpose {ProofPurpose}
   * @param documentLoader {function}
   * @param expansionMap {function}
   *
   * @returns {Promise<object>} Resolves with the created proof object.
   */
  async createProof(
    {document, purpose, documentLoader, expansionMap}) {
    // build proof (currently known as `signature options` in spec)
    let proof;
    if(this.proof) {
      // shallow copy
      proof = {...this.proof};
    } else {
      // create proof JSON-LD document
      proof = {};
    }

    // ensure proof type is set
    proof.type = this.type;

    // set default `now` date if not given in `proof` or `options`
    let date = this.date;
    if(proof.created === undefined && date === undefined) {
      date = new Date();
    }

    // ensure date is in string format
    if(date && typeof date !== 'string') {
      date = util.w3cDate(date);
    }

    // add API overrides
    if(date) {
      proof.created = date;
    }

    proof.verificationMethod = this.verificationMethod;

    // add any extensions to proof (mostly for legacy support)
    proof = await this.updateProof({
      document, proof, purpose, documentLoader, expansionMap
    });

    // allow purpose to update the proof; the `proof` is in the
    // SECURITY_CONTEXT_URL `@context` -- therefore the `purpose` must
    // ensure any added fields are also represented in that same `@context`
    proof = await purpose.update(
      proof, {document, suite: this, documentLoader, expansionMap});

    // create data to sign
    const verifyData = await this.createVerifyData({
      document, proof, documentLoader, expansionMap
    });

    // sign data
    proof = await this.sign(
      {verifyData, document, proof, documentLoader, expansionMap});

    return proof;
  }

  /**
   * @param document {object} to be signed.
   * @param purpose {ProofPurpose}
   * @param documentLoader {function}
   * @param expansionMap {function}
   *
   * @returns {Promise<object>} Resolves with the created proof object.
   */
  async updateProof({proof}) {
    // extending classes may do more
    return proof;
  }

  /**
   * @param proof {object} the proof to be verified.
   * @param document {object} the document the proof applies to.
   * @param purpose {ProofPurpose}
   * @param documentLoader {function}
   * @param expansionMap {function}
   *
   * @returns {Promise<{object}>} Resolves with the verification result.
   */
  async verifyProof({
    proof, document, purpose, documentLoader, expansionMap,
  }) {
    try {
      // create data to verify
      const verifyData = await this.createVerifyData(
        {document, proof, documentLoader, expansionMap});

      // fetch verification method
      const verificationMethod = await this.getVerificationMethod(
        {proof, document, documentLoader, expansionMap});

      // verify signature on data
      const verified = await this.verifySignature({
        verifyData, verificationMethod, document, proof,
        documentLoader, expansionMap});
      if(!verified) {
        throw new Error('Invalid signature.');
      }

      // ensure proof was performed for a valid purpose
      const purposeResult = await purpose.validate(
        proof, {document, suite: this, verificationMethod,
          documentLoader, expansionMap});
      if(!purposeResult.valid) {
        throw purposeResult.error;
      }

      return {verified: true, purposeResult};
    } catch(error) {
      return {verified: false, error};
    }
  }

  async canonize(input, {documentLoader, expansionMap, skipExpansion}) {
    return jsonld.canonize(input, {
      algorithm: 'URDNA2015',
      format: 'application/n-quads',
      documentLoader,
      expansionMap,
      skipExpansion,
      useNative: this.useNativeCanonize
    });
  }

  async canonizeProof(proof, {document, documentLoader, expansionMap}) {
    // `jws`,`signatureValue`,`proofValue` must not be included in the proof
    // options
    proof = {
      '@context': document['@context'] || constants.SECURITY_CONTEXT_URL,
      ...proof
    };
    delete proof.jws;
    delete proof.signatureValue;
    delete proof.proofValue;
    return this.canonize(proof, {
      documentLoader,
      expansionMap,
      skipExpansion: false
    });
  }

  /**
   * @param document {object} to be signed/verified.
   * @param proof {object}
   * @param documentLoader {function}
   * @param expansionMap {function}
   *
   * @returns {Promise<{Uint8Array}>}.
   */
  async createVerifyData({
    document, proof, documentLoader, expansionMap}) {
    // concatenate hash of c14n proof options and hash of c14n document
    const c14nProofOptions = await this.canonizeProof(
      proof, {document, documentLoader, expansionMap});
    const c14nDocument = await this.canonize(document, {
      documentLoader,
      expansionMap
    });
    return util.concat(
      await sha256digest({string: c14nProofOptions}),
      await sha256digest({string: c14nDocument}));
  }

  /**
   * @param document {object} to be signed.
   * @param proof {object}
   * @param documentLoader {function}
   */
  async getVerificationMethod({proof, documentLoader}) {
    let {verificationMethod} = proof;

    if(typeof verificationMethod === 'object') {
      verificationMethod = verificationMethod.id;
    }

    if(!verificationMethod) {
      throw new Error('No "verificationMethod" found in proof.');
    }

    // Note: `expansionMap` is intentionally not passed; we can safely drop
    // properties here and must allow for it
    const framed = await jsonld.frame(verificationMethod, {
      '@context': constants.SECURITY_CONTEXT_URL,
      '@embed': '@always',
      id: verificationMethod
    }, {documentLoader, compactToRelative: false});
    if(!framed) {
      throw new Error(`Verification method ${verificationMethod} not found.`);
    }

    // ensure verification method has not been revoked
    if(framed.revoked !== undefined) {
      throw new Error('The verification method has been revoked.');
    }

    return framed;
  }

  /**
   * @param verifyData {Uint8Array}.
   * @param document {object} to be signed.
   * @param proof {object}
   * @param documentLoader {function}
   * @param expansionMap {function}
   *
   * @returns {Promise<{object}>} the proof containing the signature value.
   */
  async sign() {
    throw new Error('Must be implemented by a derived class.');
  }

  /**
   * @param verifyData {Uint8Array}.
   * @param verificationMethod {object}.
   * @param document {object} to be signed.
   * @param proof {object}
   * @param documentLoader {function}
   * @param expansionMap {function}
   *
   * @returns {Promise<boolean>}
   */
  async verifySignature() {
    throw new Error('Must be implemented by a derived class.');
  }

  /**
   * See constructor docstring for param details.
   *
   * @returns {{verificationMethod: string, key: LDKeyPair, signer: Function,
   *   verifier: Function}} - Validated and initialized key-related parameters.
   */
  _processSignatureParams({key, signer, verifier}) {
    if(!(key || signer || verifier)) {
      throw new TypeError('Either a "key" or at least one of "signer"' +
        '/"verifier" is required');
    }
    if((key && signer) || (key && verifier)) {
      throw new TypeError('Either a "key" or at least one of "signer"' +
        '/"verifier" is required, but not both.');
    }

    const vm = {};
    if(key) {
      vm.key = key;
      vm.verificationMethod = key.id;
      if(typeof key.signer === 'function') {
        vm.signer = key.signer;
      }
      if(typeof key.verifier === 'function') {
        vm.verifier = key.verifier;
      }
      if(!(vm.signer || vm.verifier)) {
        throw new TypeError('The "key" parameter must contain a "signer" or ' +
          '"verifier" method.');
      }
    } else {
      vm.verificationMethod = signer && signer.id || verifier && verifier.id;
      vm.signer = signer;
      vm.verifier = verifier;
    }

    if(!(vm.verificationMethod && typeof vm.verificationMethod === 'string')) {
      // This means that verificationMethod was not passed in directly,
      // AND that neither key.id or signer.id/verifier.id exists.
      throw new TypeError('A "key.id", "signer.id" or "verifier.id" ' +
        'must be provided.');
    }

    return vm;
  }
};
<commit_msg>Add additional signer/verifier validation.<commit_after>/*!
 * Copyright (c) 2017-2018 Digital Bazaar, Inc. All rights reserved.
 */
'use strict';

const constants = require('../constants');
const jsonld = require('jsonld');
const util = require('../util');
const {sha256digest} = require('../sha256digest');
const LinkedDataProof = require('./LinkedDataProof');

module.exports = class LinkedDataSignature extends LinkedDataProof {
  /**
   * @param {object} options - Options hashmap.
   * @param {string} options.type - Suite name, provided by subclass.
   * @typedef LDKeyPair
   * @param {LDKeyPair} LDKeyClass - The crypto-ld key class that this suite
   *   will use to sign/verify signatures. Provided by subclass. Used
   *   during the `verifySignature` operation, to create an instance (containing
   *   a `verifier()` property) of a public key fetched via a `documentLoader`.
   *
   * Either a `key` OR at least one of `signer`/`verifier` is required:
   *
   * @param {object} [options.key] - An optional key object (containing an
   *   `id` property, and either `signer` or `verifier`, depending on the
   *   intended operation. Useful for when the application is managing keys
   *   itself (when using a KMS, you never have access to the private key,
   *   and so should use the `signer` param instead).
   * @param {Function} [options.signer] - Signer function that returns an
   *   object with an async sign() method. This is useful when interfacing
   *   with a KMS (since you don't get access to the private key and its
   *   `signer()`, the KMS client gives you only the signer function to use).
   * @param {Function} [options.verifier] - Verifier function that returns
   *   an object with an async `verify()` method. Useful when working with a
   *   KMS-provided verifier function.
   *
   * Advanced optional parameters and overrides:
   *
   * @param {object} [options.proof] - A JSON-LD document with options to use
   *   for the `proof` node (e.g. any other custom fields can be provided here
   *   using a context different from security-v2). If not provided, this is
   *   constructed during signing.
   * @param {string|Date} [options.date] - Signing date to use if not passed.
   * @param {boolean} [options.useNativeCanonize] - Whether to use a native
   *   canonize algorithm.
   */
  constructor({
    type, proof, LDKeyClass, date, key, signer, verifier,
    useNativeCanonize
  } = {}) {
    super({type});
    this.LDKeyClass = LDKeyClass;
    this.proof = proof;
    const vm = this._processSignatureParams({key, signer, verifier});
    this.verificationMethod = vm.verificationMethod;
    this.key = vm.key;
    this.signer = vm.signer;
    this.verifier = vm.verifier;
    if(date) {
      this.date = new Date(date);
      if(isNaN(this.date)) {
        throw TypeError(`"date" "${date}" is not a valid date.`);
      }
    }
    this.useNativeCanonize = useNativeCanonize;
  }

  /**
   * @param document {object} to be signed.
   * @param purpose {ProofPurpose}
   * @param documentLoader {function}
   * @param expansionMap {function}
   *
   * @returns {Promise<object>} Resolves with the created proof object.
   */
  async createProof(
    {document, purpose, documentLoader, expansionMap}) {
    // build proof (currently known as `signature options` in spec)
    let proof;
    if(this.proof) {
      // shallow copy
      proof = {...this.proof};
    } else {
      // create proof JSON-LD document
      proof = {};
    }

    // ensure proof type is set
    proof.type = this.type;

    // set default `now` date if not given in `proof` or `options`
    let date = this.date;
    if(proof.created === undefined && date === undefined) {
      date = new Date();
    }

    // ensure date is in string format
    if(date && typeof date !== 'string') {
      date = util.w3cDate(date);
    }

    // add API overrides
    if(date) {
      proof.created = date;
    }

    proof.verificationMethod = this.verificationMethod;

    // add any extensions to proof (mostly for legacy support)
    proof = await this.updateProof({
      document, proof, purpose, documentLoader, expansionMap
    });

    // allow purpose to update the proof; the `proof` is in the
    // SECURITY_CONTEXT_URL `@context` -- therefore the `purpose` must
    // ensure any added fields are also represented in that same `@context`
    proof = await purpose.update(
      proof, {document, suite: this, documentLoader, expansionMap});

    // create data to sign
    const verifyData = await this.createVerifyData({
      document, proof, documentLoader, expansionMap
    });

    // sign data
    proof = await this.sign(
      {verifyData, document, proof, documentLoader, expansionMap});

    return proof;
  }

  /**
   * @param document {object} to be signed.
   * @param purpose {ProofPurpose}
   * @param documentLoader {function}
   * @param expansionMap {function}
   *
   * @returns {Promise<object>} Resolves with the created proof object.
   */
  async updateProof({proof}) {
    // extending classes may do more
    return proof;
  }

  /**
   * @param proof {object} the proof to be verified.
   * @param document {object} the document the proof applies to.
   * @param purpose {ProofPurpose}
   * @param documentLoader {function}
   * @param expansionMap {function}
   *
   * @returns {Promise<{object}>} Resolves with the verification result.
   */
  async verifyProof({
    proof, document, purpose, documentLoader, expansionMap,
  }) {
    try {
      // create data to verify
      const verifyData = await this.createVerifyData(
        {document, proof, documentLoader, expansionMap});

      // fetch verification method
      const verificationMethod = await this.getVerificationMethod(
        {proof, document, documentLoader, expansionMap});

      // verify signature on data
      const verified = await this.verifySignature({
        verifyData, verificationMethod, document, proof,
        documentLoader, expansionMap});
      if(!verified) {
        throw new Error('Invalid signature.');
      }

      // ensure proof was performed for a valid purpose
      const purposeResult = await purpose.validate(
        proof, {document, suite: this, verificationMethod,
          documentLoader, expansionMap});
      if(!purposeResult.valid) {
        throw purposeResult.error;
      }

      return {verified: true, purposeResult};
    } catch(error) {
      return {verified: false, error};
    }
  }

  async canonize(input, {documentLoader, expansionMap, skipExpansion}) {
    return jsonld.canonize(input, {
      algorithm: 'URDNA2015',
      format: 'application/n-quads',
      documentLoader,
      expansionMap,
      skipExpansion,
      useNative: this.useNativeCanonize
    });
  }

  async canonizeProof(proof, {document, documentLoader, expansionMap}) {
    // `jws`,`signatureValue`,`proofValue` must not be included in the proof
    // options
    proof = {
      '@context': document['@context'] || constants.SECURITY_CONTEXT_URL,
      ...proof
    };
    delete proof.jws;
    delete proof.signatureValue;
    delete proof.proofValue;
    return this.canonize(proof, {
      documentLoader,
      expansionMap,
      skipExpansion: false
    });
  }

  /**
   * @param document {object} to be signed/verified.
   * @param proof {object}
   * @param documentLoader {function}
   * @param expansionMap {function}
   *
   * @returns {Promise<{Uint8Array}>}.
   */
  async createVerifyData({
    document, proof, documentLoader, expansionMap}) {
    // concatenate hash of c14n proof options and hash of c14n document
    const c14nProofOptions = await this.canonizeProof(
      proof, {document, documentLoader, expansionMap});
    const c14nDocument = await this.canonize(document, {
      documentLoader,
      expansionMap
    });
    return util.concat(
      await sha256digest({string: c14nProofOptions}),
      await sha256digest({string: c14nDocument}));
  }

  /**
   * @param document {object} to be signed.
   * @param proof {object}
   * @param documentLoader {function}
   */
  async getVerificationMethod({proof, documentLoader}) {
    let {verificationMethod} = proof;

    if(typeof verificationMethod === 'object') {
      verificationMethod = verificationMethod.id;
    }

    if(!verificationMethod) {
      throw new Error('No "verificationMethod" found in proof.');
    }

    // Note: `expansionMap` is intentionally not passed; we can safely drop
    // properties here and must allow for it
    const framed = await jsonld.frame(verificationMethod, {
      '@context': constants.SECURITY_CONTEXT_URL,
      '@embed': '@always',
      id: verificationMethod
    }, {documentLoader, compactToRelative: false});
    if(!framed) {
      throw new Error(`Verification method ${verificationMethod} not found.`);
    }

    // ensure verification method has not been revoked
    if(framed.revoked !== undefined) {
      throw new Error('The verification method has been revoked.');
    }

    return framed;
  }

  /**
   * @param verifyData {Uint8Array}.
   * @param document {object} to be signed.
   * @param proof {object}
   * @param documentLoader {function}
   * @param expansionMap {function}
   *
   * @returns {Promise<{object}>} the proof containing the signature value.
   */
  async sign() {
    throw new Error('Must be implemented by a derived class.');
  }

  /**
   * @param verifyData {Uint8Array}.
   * @param verificationMethod {object}.
   * @param document {object} to be signed.
   * @param proof {object}
   * @param documentLoader {function}
   * @param expansionMap {function}
   *
   * @returns {Promise<boolean>}
   */
  async verifySignature() {
    throw new Error('Must be implemented by a derived class.');
  }

  /**
   * See constructor docstring for param details.
   *
   * @returns {{verificationMethod: string, key: LDKeyPair, signer: Function,
   *   verifier: Function}} - Validated and initialized key-related parameters.
   */
  _processSignatureParams({key, signer, verifier}) {
    if(!(key || signer || verifier)) {
      throw new TypeError('Either a "key" or at least one of "signer"' +
        '/"verifier" is required');
    }
    if((key && signer) || (key && verifier)) {
      throw new TypeError('Either a "key" or at least one of "signer"' +
        '/"verifier" is required, but not both.');
    }

    const vm = {};
    if(key) {
      vm.key = key;
      vm.verificationMethod = key.id;
      if(typeof key.signer === 'function') {
        vm.signer = key.signer();
      }
      if(typeof key.verifier === 'function') {
        vm.verifier = key.verifier();
      }
      if(!(vm.signer || vm.verifier)) {
        throw new TypeError('The "key" parameter must contain a "signer" or ' +
          '"verifier" method.');
      }
    } else {
      vm.verificationMethod = signer && signer.id || verifier && verifier.id;

      vm.signer = signer();
      vm.verifier = verifier();
    }

    if(vm.signer) {
      if(typeof vm.signer.sign !== 'function') {
        throw new TypeError('A signer API has not been specified.');
      }
    }
    if(vm.verifier) {
      if(typeof vm.verifier.verify !== 'function') {
        throw new TypeError('A verifier API has not been specified.');
      }
    }

    if(!(vm.verificationMethod && typeof vm.verificationMethod === 'string')) {
      // This means that verificationMethod was not passed in directly,
      // AND that neither key.id or signer.id/verifier.id exists.
      throw new TypeError('A "key.id", "signer.id" or "verifier.id" ' +
        'must be provided.');
    }

    return vm;
  }
};
<|endoftext|>