<commit_before>"use strict";

if (typeof (JSIL) === "undefined")
  throw new Error("JSIL.Core required");

if (typeof ($jsilxna) === "undefined")
  throw new Error("JSIL.XNACore required");

JSIL.MakeClass($jsilcore.System.Object, "VirtualVolume", true, [], function ($) {
  $.RawMethod(false, ".ctor", function (name) {
  	this.name = name;
  	this.rootDirectory = new VirtualDirectory(this);
  });

  $.RawMethod(false, "stripRoot", function (path) {
  	var indexOfRoot = path.indexOf(this.rootDirectory.path);
  	if (indexOfRoot === 0)
  		path = path.substr(this.rootDirectory.path.length);

  	return path;
  });

  $.RawMethod(false, "createDirectory", function (path) {
  	path = this.stripRoot(path);

  	var pieces = path.split("/");

  	for (var i = 0, l = pieces.length; i < l; i++) {
  		var containingPath = pieces.slice(0, i).join("/") + "/";

  		var containingDirectory = this.rootDirectory.resolvePath(containingPath);
  		containingDirectory.createDirectory(pieces[i], true);
  	}

  	return this.rootDirectory.resolvePath(path);
  });

  $.RawMethod(false, "resolvePath", function (path, throwOnFail) {
  	path = this.stripRoot(path);

  	return this.rootDirectory.resolvePath(path, throwOnFail);
  });

  $.RawMethod(false, "toString", function () {
    return "<Virtual Storage Volume '" + this.name + "'>";
  });
});

JSIL.MakeClass($jsilcore.System.Object, "VirtualDirectory", true, [], function ($) {
  $.RawMethod(false, ".ctor", function (volume, parent, name) {
  	this.volume = volume;
  	this.parent = parent || null;
  	this.name = parent ? name : "/";
  	this.path = parent ? parent.path + (name + "/") : "/";

  	this.directories = {};
  	this.files = {};
  });

  $.RawMethod(false, "getFile", function (name) {
  	var file = this.files[name.toLowerCase()];
  	if (!file)
  		return null;

  	return file;
  });

  $.RawMethod(false, "getDirectory", function (name) {
  	if ((name === ".") || (name === ""))
  		return this;
  	else if (name === "..")
  		return this.parent;

  	var directory = this.directories[name.toLowerCase()];
  	if (!directory)
  		return null;

  	return directory;
  });

  $.RawMethod(false, "createFile", function (name, allowExisting) {
  	var existingFile = this.getFile(name);
  	if (existingFile) {
  		if (allowExisting)
  			return existingFile;
  		else
  			throw new Error("A file named '" + name + "' already exists.");
  	}

  	return this.files[name.toLowerCase()] = new VirtualFile(this, name);
  });

  $.RawMethod(false, "createDirectory", function (name, allowExisting) {
  	var existingDirectory = this.getDirectory(name);
  	if (existingDirectory) {
  		if (allowExisting)
  			return existingDirectory;
  		else
  			throw new Error("A directory named '" + name + "' already exists.");
  	}

  	return this.directories[name.toLowerCase()] = new VirtualDirectory(this.volume, this, name);
  });

  $.RawMethod(false, "resolvePath", function (path, throwOnFail) {
  	var firstSlash = path.indexOf("/"), itemName, childPath;

  	if (typeof(throwOnFail) === "undefined")
  		throwOnFail = true;

  	if (firstSlash >= 0) {
  		itemName = path.substr(0, firstSlash);
  		childPath = path.substr(firstSlash);
  	} else {
  		itemName = path;
  		childPath = null;
  	}

  	var forceDirectory = childPath === "/";
  	if (forceDirectory) {
  		childPath = null;
  	} else if (childPath) {
  		while (childPath[0] === "/")
  			childPath = childPath.substr(1);
  	}

  	var result = this.getDirectory(itemName);
  	if (result === null) {
  		if (forceDirectory) {
  			if (throwOnFail)
  				throw new Error("No directory named '" + itemName + "' could be found in directory '" + this.path + "'.");
  			else
  				result = null;
		} else
  			result = this.getFile(itemName);
  	}

  	if (childPath) {
  		if (result) {
  			return result.resolvePath(childPath, throwOnFail);
  		} else {
  			if (throwOnFail)
  				throw new Error("No directory named '" + itemName + "' could be found in directory '" + this.path + "'.");
  			else
  				return null;
  		}
  	} else {
  		if (!result && throwOnFail)
			throw new Error("No file or directory named '" + itemName + "' could be found in directory '" + this.path + "'.");

  		return result;
  	}
  });

  $.RawMethod(false, "toString", function () {
    return "<Virtual Directory '" + this.path + "' in volume '" + this.volume.name + "'>";
  });
});

JSIL.MakeClass($jsilcore.System.Object, "VirtualFile", true, [], function ($) {
  $.RawMethod(false, ".ctor", function (parent, name) {
 	this.parent = parent;
  	this.name = name;
  	this.path = parent.path + name;
  });

  $.RawMethod(false, "toString", function () {
    return "<Virtual File '" + this.path + "' in volume '" + this.parent.volume.name + "'>";
  });
});

JSIL.ImplementExternals("Microsoft.Xna.Framework.Storage.StorageContainer", function ($) {

  $.Method({Static:false, Public:false}, ".ctor",
    (new JSIL.MethodSignature(null, [
          $xnaasms.xnaStorage.TypeRef("Microsoft.Xna.Framework.Storage.StorageDevice"), $xnaasms.xna.TypeRef("Microsoft.Xna.Framework.PlayerIndex"),
          $.String
        ], [])),
    function _ctor (device, index, displayName) {
      // FIXME
    }
  );

  $.Method({Static:false, Public:true }, "add_Disposing",
    (new JSIL.MethodSignature(null, [$xnaasms.corlib.TypeRef("System.EventHandler`1", [$xnaasms.corlib.TypeRef("System.EventArgs")])], [])),
    function add_Disposing (value) {
      // FIXME
    }
  );

  $.Method({Static:false, Public:true }, "CreateDirectory",
    (new JSIL.MethodSignature(null, [$.String], [])),
    function CreateDirectory (directory) {
      throw new Error('Not implemented');
    }
  );

  $.Method({Static:false, Public:true }, "CreateFile",
    (new JSIL.MethodSignature($xnaasms.corlib.TypeRef("System.IO.Stream"), [$.String], [])),
    function CreateFile (file) {
      throw new Error('Not implemented');
    }
  );

  $.Method({Static:true , Public:false}, "DeleteContainer",
    (new JSIL.MethodSignature(null, [
          $xnaasms.xnaStorage.TypeRef("Microsoft.Xna.Framework.Storage.StorageDevice"), $xnaasms.xna.TypeRef("Microsoft.Xna.Framework.PlayerIndex"),
          $.String, $.Boolean
        ], [])),
    function DeleteContainer (device, index, displayName, closeContainerFirst) {
      throw new Error('Not implemented');
    }
  );

  $.Method({Static:false, Public:true }, "DeleteDirectory",
    (new JSIL.MethodSignature(null, [$.String], [])),
    function DeleteDirectory (directory) {
      throw new Error('Not implemented');
    }
  );

  $.Method({Static:false, Public:true }, "DeleteFile",
    (new JSIL.MethodSignature(null, [$.String], [])),
    function DeleteFile (file) {
      throw new Error('Not implemented');
    }
  );

  $.Method({Static:false, Public:true }, "DirectoryExists",
    (new JSIL.MethodSignature($.Boolean, [$.String], [])),
    function DirectoryExists (directory) {
      // FIXME
      return false;
    }
  );

  $.Method({Static:false, Public:true }, "Dispose",
    (new JSIL.MethodSignature(null, [], [])),
    function Dispose () {
      // FIXME
    }
  );

  $.Method({Static:false, Public:false}, "Dispose",
    (new JSIL.MethodSignature(null, [$.Boolean], [])),
    function Dispose (disposing) {
      // FIXME
    }
  );

  $.Method({Static:false, Public:false}, "DisposeOverride",
    (new JSIL.MethodSignature(null, [$.Boolean], [])),
    function DisposeOverride (disposing) {
      // FIXME
    }
  );

  $.Method({Static:false, Public:true }, "FileExists",
    (new JSIL.MethodSignature($.Boolean, [$.String], [])),
    function FileExists (file) {
      // FIXME
      return false;
    }
  );

  $.Method({Static:false, Public:false}, "Finalize",
    (new JSIL.MethodSignature(null, [], [])),
    function Finalize () {
      // FIXME
    }
  );

  $.Method({Static:false, Public:false}, "FinishCreation",
    (new JSIL.MethodSignature(null, [], [])),
    function FinishCreation () {
      throw new Error('Not implemented');
    }
  );

  $.Method({Static:false, Public:true }, "get_DisplayName",
    (new JSIL.MethodSignature($.String, [], [])),
    function get_DisplayName () {
      throw new Error('Not implemented');
    }
  );

  $.Method({Static:false, Public:true }, "get_IsDisposed",
    (new JSIL.MethodSignature($.Boolean, [], [])),
    function get_IsDisposed () {
      throw new Error('Not implemented');
    }
  );

  $.Method({Static:false, Public:true }, "get_StorageDevice",
    (new JSIL.MethodSignature($xnaasms.xnaStorage.TypeRef("Microsoft.Xna.Framework.Storage.StorageDevice"), [], [])),
    function get_StorageDevice () {
      throw new Error('Not implemented');
    }
  );

  $.Method({Static:true , Public:false}, "GetDeviceFolder",
    (new JSIL.MethodSignature($.String, [$.UInt32], [])),
    function GetDeviceFolder (deviceIndex) {
      throw new Error('Not implemented');
    }
  );

  $.Method({Static:true , Public:false}, "GetDirectoryForContainer",
    (new JSIL.MethodSignature($xnaasms.corlib.TypeRef("System.IO.DirectoryInfo"), [
          $xnaasms.xnaStorage.TypeRef("Microsoft.Xna.Framework.Storage.StorageDevice"), $.String,
          $xnaasms.xna.TypeRef("Microsoft.Xna.Framework.PlayerIndex")
        ], [])),
    function GetDirectoryForContainer (device, displayName, playerIndex) {
      throw new Error('Not implemented');
    }
  );

  $.Method({Static:false, Public:true }, "GetDirectoryNames",
    (new JSIL.MethodSignature($jsilcore.TypeRef("System.Array", [$.String]), [], [])),
    function GetDirectoryNames () {
      throw new Error('Not implemented');
    }
  );

  $.Method({Static:false, Public:true }, "GetDirectoryNames",
    (new JSIL.MethodSignature($jsilcore.TypeRef("System.Array", [$.String]), [$.String], [])),
    function GetDirectoryNames (searchPattern) {
      throw new Error('Not implemented');
    }
  );

  $.Method({Static:false, Public:true }, "GetFileNames",
    (new JSIL.MethodSignature($jsilcore.TypeRef("System.Array", [$.String]), [], [])),
    function GetFileNames () {
      throw new Error('Not implemented');
    }
  );

  $.Method({Static:false, Public:true }, "GetFileNames",
    (new JSIL.MethodSignature($jsilcore.TypeRef("System.Array", [$.String]), [$.String], [])),
    function GetFileNames (searchPattern) {
      throw new Error('Not implemented');
    }
  );

  $.Method({Static:false, Public:false}, "GetFullPath",
    (new JSIL.MethodSignature($.String, [$.String], [])),
    function GetFullPath (path) {
      throw new Error('Not implemented');
    }
  );

  $.Method({Static:true , Public:false}, "GetGameTitle",
    (new JSIL.MethodSignature($.String, [], [])),
    function GetGameTitle () {
      throw new Error('Not implemented');
    }
  );

  $.Method({Static:false, Public:true }, "OpenFile",
    (new JSIL.MethodSignature($xnaasms.corlib.TypeRef("System.IO.Stream"), [$.String, $xnaasms.corlib.TypeRef("System.IO.FileMode")], [])),
    function OpenFile (file, fileMode) {
      throw new Error('Not implemented');
    }
  );

  $.Method({Static:false, Public:true }, "OpenFile",
    (new JSIL.MethodSignature($xnaasms.corlib.TypeRef("System.IO.Stream"), [
          $.String, $xnaasms.corlib.TypeRef("System.IO.FileMode"),
          $xnaasms.corlib.TypeRef("System.IO.FileAccess")
        ], [])),
    function OpenFile (file, fileMode, fileAccess) {
      throw new Error('Not implemented');
    }
  );

  $.Method({Static:false, Public:true }, "OpenFile",
    (new JSIL.MethodSignature($xnaasms.corlib.TypeRef("System.IO.Stream"), [
          $.String, $xnaasms.corlib.TypeRef("System.IO.FileMode"),
          $xnaasms.corlib.TypeRef("System.IO.FileAccess"), $xnaasms.corlib.TypeRef("System.IO.FileShare")
        ], [])),
    function OpenFile (file, fileMode, fileAccess, fileShare) {
      throw new Error('Not implemented');
    }
  );

  $.Method({Static:false, Public:true }, "remove_Disposing",
    (new JSIL.MethodSignature(null, [$xnaasms.corlib.TypeRef("System.EventHandler`1", [$xnaasms.corlib.TypeRef("System.EventArgs")])], [])),
    function remove_Disposing (value) {
      throw new Error('Not implemented');
    }
  );

  $.Method({Static:false, Public:false}, "ValidateArguments",
    (new JSIL.MethodSignature($.String, [$.String, $.String], [])),
    function ValidateArguments (path, argumentName) {
      throw new Error('Not implemented');
    }
  );

  $.Method({Static:false, Public:false}, "VerifyNotDisposed",
    (new JSIL.MethodSignature(null, [], [])),
    function VerifyNotDisposed () {
      throw new Error('Not implemented');
    }
  );

});<commit_msg>Add createFile method for creating a file within an existing directory.<commit_after>"use strict";

if (typeof (JSIL) === "undefined")
  throw new Error("JSIL.Core required");

if (typeof ($jsilxna) === "undefined")
  throw new Error("JSIL.XNACore required");

JSIL.MakeClass($jsilcore.System.Object, "VirtualVolume", true, [], function ($) {
  $.RawMethod(false, ".ctor", function (name) {
  	this.name = name;
  	this.rootDirectory = new VirtualDirectory(this);
  });

  $.RawMethod(false, "stripRoot", function (path) {
  	var indexOfRoot = path.indexOf(this.rootDirectory.path);
  	if (indexOfRoot === 0)
  		path = path.substr(this.rootDirectory.path.length);

  	return path;
  });

  $.RawMethod(false, "createDirectory", function (path) {
  	path = this.stripRoot(path);

  	var pieces = path.split("/");

  	for (var i = 0, l = pieces.length; i < l; i++) {
  		var containingPath = pieces.slice(0, i).join("/") + "/";

  		var containingDirectory = this.rootDirectory.resolvePath(containingPath);
  		containingDirectory.createDirectory(pieces[i], true);
  	}

  	return this.rootDirectory.resolvePath(path);
  });

  $.RawMethod(false, "createFile", function (path, allowExisting) {
  	path = this.stripRoot(path);

  	var lastSlash = path.lastIndexOf("/"), parentDirectory, fileName;
  	if (lastSlash >= 0) {
  		parentDirectory = this.rootDirectory.resolvePath(path.substr(0, lastSlash), true);
  		fileName = path.substr(lastSlash + 1);
  	} else {
  		parentDirectory = this.rootDirectory;
  		fileName = path;
  	}

	return parentDirectory.createFile(fileName, allowExisting);
  });

  $.RawMethod(false, "resolvePath", function (path, throwOnFail) {
  	path = this.stripRoot(path);

  	return this.rootDirectory.resolvePath(path, throwOnFail);
  });

  $.RawMethod(false, "toString", function () {
    return "<Virtual Storage Volume '" + this.name + "'>";
  });
});

JSIL.MakeClass($jsilcore.System.Object, "VirtualDirectory", true, [], function ($) {
  $.RawMethod(false, ".ctor", function (volume, parent, name) {
  	this.volume = volume;
  	this.parent = parent || null;
  	this.name = parent ? name : "/";
  	this.path = parent ? parent.path + (name + "/") : "/";

  	this.directories = {};
  	this.files = {};
  });

  $.RawMethod(false, "getFile", function (name) {
  	var file = this.files[name.toLowerCase()];
  	if (!file)
  		return null;

  	return file;
  });

  $.RawMethod(false, "getDirectory", function (name) {
  	if ((name === ".") || (name === ""))
  		return this;
  	else if (name === "..")
  		return this.parent;

  	var directory = this.directories[name.toLowerCase()];
  	if (!directory)
  		return null;

  	return directory;
  });

  $.RawMethod(false, "createFile", function (name, allowExisting) {
  	var existingFile = this.getFile(name);
  	if (existingFile) {
  		if (allowExisting)
  			return existingFile;
  		else
  			throw new Error("A file named '" + name + "' already exists.");
  	}

  	return this.files[name.toLowerCase()] = new VirtualFile(this, name);
  });

  $.RawMethod(false, "createDirectory", function (name, allowExisting) {
  	var existingDirectory = this.getDirectory(name);
  	if (existingDirectory) {
  		if (allowExisting)
  			return existingDirectory;
  		else
  			throw new Error("A directory named '" + name + "' already exists.");
  	}

  	return this.directories[name.toLowerCase()] = new VirtualDirectory(this.volume, this, name);
  });

  $.RawMethod(false, "resolvePath", function (path, throwOnFail) {
  	var firstSlash = path.indexOf("/"), itemName, childPath;

  	if (typeof(throwOnFail) === "undefined")
  		throwOnFail = true;

  	if (firstSlash >= 0) {
  		itemName = path.substr(0, firstSlash);
  		childPath = path.substr(firstSlash);
  	} else {
  		itemName = path;
  		childPath = null;
  	}

  	var forceDirectory = childPath === "/";
  	if (forceDirectory) {
  		childPath = null;
  	} else if (childPath) {
  		while (childPath[0] === "/")
  			childPath = childPath.substr(1);
  	}

  	var result = this.getDirectory(itemName);
  	if (result === null) {
  		if (forceDirectory) {
  			if (throwOnFail)
  				throw new Error("No directory named '" + itemName + "' could be found in directory '" + this.path + "'.");
  			else
  				result = null;
		} else
  			result = this.getFile(itemName);
  	}

  	if (childPath) {
  		if (result) {
  			return result.resolvePath(childPath, throwOnFail);
  		} else {
  			if (throwOnFail)
  				throw new Error("No directory named '" + itemName + "' could be found in directory '" + this.path + "'.");
  			else
  				return null;
  		}
  	} else {
  		if (!result && throwOnFail)
			throw new Error("No file or directory named '" + itemName + "' could be found in directory '" + this.path + "'.");

  		return result;
  	}
  });

  $.RawMethod(false, "toString", function () {
    return "<Virtual Directory '" + this.path + "' in volume '" + this.volume.name + "'>";
  });
});

JSIL.MakeClass($jsilcore.System.Object, "VirtualFile", true, [], function ($) {
  $.RawMethod(false, ".ctor", function (parent, name) {
 	this.parent = parent;
  	this.name = name;
  	this.path = parent.path + name;
  });

  $.RawMethod(false, "toString", function () {
    return "<Virtual File '" + this.path + "' in volume '" + this.parent.volume.name + "'>";
  });
});

JSIL.ImplementExternals("Microsoft.Xna.Framework.Storage.StorageContainer", function ($) {

  $.Method({Static:false, Public:false}, ".ctor",
    (new JSIL.MethodSignature(null, [
          $xnaasms.xnaStorage.TypeRef("Microsoft.Xna.Framework.Storage.StorageDevice"), $xnaasms.xna.TypeRef("Microsoft.Xna.Framework.PlayerIndex"),
          $.String
        ], [])),
    function _ctor (device, index, displayName) {
      // FIXME
    }
  );

  $.Method({Static:false, Public:true }, "add_Disposing",
    (new JSIL.MethodSignature(null, [$xnaasms.corlib.TypeRef("System.EventHandler`1", [$xnaasms.corlib.TypeRef("System.EventArgs")])], [])),
    function add_Disposing (value) {
      // FIXME
    }
  );

  $.Method({Static:false, Public:true }, "CreateDirectory",
    (new JSIL.MethodSignature(null, [$.String], [])),
    function CreateDirectory (directory) {
      throw new Error('Not implemented');
    }
  );

  $.Method({Static:false, Public:true }, "CreateFile",
    (new JSIL.MethodSignature($xnaasms.corlib.TypeRef("System.IO.Stream"), [$.String], [])),
    function CreateFile (file) {
      throw new Error('Not implemented');
    }
  );

  $.Method({Static:true , Public:false}, "DeleteContainer",
    (new JSIL.MethodSignature(null, [
          $xnaasms.xnaStorage.TypeRef("Microsoft.Xna.Framework.Storage.StorageDevice"), $xnaasms.xna.TypeRef("Microsoft.Xna.Framework.PlayerIndex"),
          $.String, $.Boolean
        ], [])),
    function DeleteContainer (device, index, displayName, closeContainerFirst) {
      throw new Error('Not implemented');
    }
  );

  $.Method({Static:false, Public:true }, "DeleteDirectory",
    (new JSIL.MethodSignature(null, [$.String], [])),
    function DeleteDirectory (directory) {
      throw new Error('Not implemented');
    }
  );

  $.Method({Static:false, Public:true }, "DeleteFile",
    (new JSIL.MethodSignature(null, [$.String], [])),
    function DeleteFile (file) {
      throw new Error('Not implemented');
    }
  );

  $.Method({Static:false, Public:true }, "DirectoryExists",
    (new JSIL.MethodSignature($.Boolean, [$.String], [])),
    function DirectoryExists (directory) {
      // FIXME
      return false;
    }
  );

  $.Method({Static:false, Public:true }, "Dispose",
    (new JSIL.MethodSignature(null, [], [])),
    function Dispose () {
      // FIXME
    }
  );

  $.Method({Static:false, Public:false}, "Dispose",
    (new JSIL.MethodSignature(null, [$.Boolean], [])),
    function Dispose (disposing) {
      // FIXME
    }
  );

  $.Method({Static:false, Public:false}, "DisposeOverride",
    (new JSIL.MethodSignature(null, [$.Boolean], [])),
    function DisposeOverride (disposing) {
      // FIXME
    }
  );

  $.Method({Static:false, Public:true }, "FileExists",
    (new JSIL.MethodSignature($.Boolean, [$.String], [])),
    function FileExists (file) {
      // FIXME
      return false;
    }
  );

  $.Method({Static:false, Public:false}, "Finalize",
    (new JSIL.MethodSignature(null, [], [])),
    function Finalize () {
      // FIXME
    }
  );

  $.Method({Static:false, Public:false}, "FinishCreation",
    (new JSIL.MethodSignature(null, [], [])),
    function FinishCreation () {
      throw new Error('Not implemented');
    }
  );

  $.Method({Static:false, Public:true }, "get_DisplayName",
    (new JSIL.MethodSignature($.String, [], [])),
    function get_DisplayName () {
      throw new Error('Not implemented');
    }
  );

  $.Method({Static:false, Public:true }, "get_IsDisposed",
    (new JSIL.MethodSignature($.Boolean, [], [])),
    function get_IsDisposed () {
      throw new Error('Not implemented');
    }
  );

  $.Method({Static:false, Public:true }, "get_StorageDevice",
    (new JSIL.MethodSignature($xnaasms.xnaStorage.TypeRef("Microsoft.Xna.Framework.Storage.StorageDevice"), [], [])),
    function get_StorageDevice () {
      throw new Error('Not implemented');
    }
  );

  $.Method({Static:true , Public:false}, "GetDeviceFolder",
    (new JSIL.MethodSignature($.String, [$.UInt32], [])),
    function GetDeviceFolder (deviceIndex) {
      throw new Error('Not implemented');
    }
  );

  $.Method({Static:true , Public:false}, "GetDirectoryForContainer",
    (new JSIL.MethodSignature($xnaasms.corlib.TypeRef("System.IO.DirectoryInfo"), [
          $xnaasms.xnaStorage.TypeRef("Microsoft.Xna.Framework.Storage.StorageDevice"), $.String,
          $xnaasms.xna.TypeRef("Microsoft.Xna.Framework.PlayerIndex")
        ], [])),
    function GetDirectoryForContainer (device, displayName, playerIndex) {
      throw new Error('Not implemented');
    }
  );

  $.Method({Static:false, Public:true }, "GetDirectoryNames",
    (new JSIL.MethodSignature($jsilcore.TypeRef("System.Array", [$.String]), [], [])),
    function GetDirectoryNames () {
      throw new Error('Not implemented');
    }
  );

  $.Method({Static:false, Public:true }, "GetDirectoryNames",
    (new JSIL.MethodSignature($jsilcore.TypeRef("System.Array", [$.String]), [$.String], [])),
    function GetDirectoryNames (searchPattern) {
      throw new Error('Not implemented');
    }
  );

  $.Method({Static:false, Public:true }, "GetFileNames",
    (new JSIL.MethodSignature($jsilcore.TypeRef("System.Array", [$.String]), [], [])),
    function GetFileNames () {
      throw new Error('Not implemented');
    }
  );

  $.Method({Static:false, Public:true }, "GetFileNames",
    (new JSIL.MethodSignature($jsilcore.TypeRef("System.Array", [$.String]), [$.String], [])),
    function GetFileNames (searchPattern) {
      throw new Error('Not implemented');
    }
  );

  $.Method({Static:false, Public:false}, "GetFullPath",
    (new JSIL.MethodSignature($.String, [$.String], [])),
    function GetFullPath (path) {
      throw new Error('Not implemented');
    }
  );

  $.Method({Static:true , Public:false}, "GetGameTitle",
    (new JSIL.MethodSignature($.String, [], [])),
    function GetGameTitle () {
      throw new Error('Not implemented');
    }
  );

  $.Method({Static:false, Public:true }, "OpenFile",
    (new JSIL.MethodSignature($xnaasms.corlib.TypeRef("System.IO.Stream"), [$.String, $xnaasms.corlib.TypeRef("System.IO.FileMode")], [])),
    function OpenFile (file, fileMode) {
      throw new Error('Not implemented');
    }
  );

  $.Method({Static:false, Public:true }, "OpenFile",
    (new JSIL.MethodSignature($xnaasms.corlib.TypeRef("System.IO.Stream"), [
          $.String, $xnaasms.corlib.TypeRef("System.IO.FileMode"),
          $xnaasms.corlib.TypeRef("System.IO.FileAccess")
        ], [])),
    function OpenFile (file, fileMode, fileAccess) {
      throw new Error('Not implemented');
    }
  );

  $.Method({Static:false, Public:true }, "OpenFile",
    (new JSIL.MethodSignature($xnaasms.corlib.TypeRef("System.IO.Stream"), [
          $.String, $xnaasms.corlib.TypeRef("System.IO.FileMode"),
          $xnaasms.corlib.TypeRef("System.IO.FileAccess"), $xnaasms.corlib.TypeRef("System.IO.FileShare")
        ], [])),
    function OpenFile (file, fileMode, fileAccess, fileShare) {
      throw new Error('Not implemented');
    }
  );

  $.Method({Static:false, Public:true }, "remove_Disposing",
    (new JSIL.MethodSignature(null, [$xnaasms.corlib.TypeRef("System.EventHandler`1", [$xnaasms.corlib.TypeRef("System.EventArgs")])], [])),
    function remove_Disposing (value) {
      throw new Error('Not implemented');
    }
  );

  $.Method({Static:false, Public:false}, "ValidateArguments",
    (new JSIL.MethodSignature($.String, [$.String, $.String], [])),
    function ValidateArguments (path, argumentName) {
      throw new Error('Not implemented');
    }
  );

  $.Method({Static:false, Public:false}, "VerifyNotDisposed",
    (new JSIL.MethodSignature(null, [], [])),
    function VerifyNotDisposed () {
      throw new Error('Not implemented');
    }
  );

});<|endoftext|>


<commit_before>/* eslint no-unused-expressions: "off" */
/* eslint-env mocha */

'use strict';

const fs = require('fs');
const chai = require('chai');
const bondage = require('../src/bondage.js');

const expect = chai.expect;

describe('Dialogue', () => {
  let linksYarnData;
  let shortcutsYarnData;
  let assignmentYarnData;
  let conditionalYarnData;
  let commandAndFunctionYarnData;

  let runner;

  before(() => {
    linksYarnData = JSON.parse(fs.readFileSync('./tests/yarn_files/links.json'));
    shortcutsYarnData = JSON.parse(fs.readFileSync('./tests/yarn_files/shortcuts.json'));
    assignmentYarnData = JSON.parse(fs.readFileSync('./tests/yarn_files/assignment.json'));
    conditionalYarnData = JSON.parse(fs.readFileSync('./tests/yarn_files/conditions.json'));
    commandAndFunctionYarnData = JSON.parse(fs.readFileSync('./tests/yarn_files/commandsandfunctions.json'));
  });

  beforeEach(() => {
    runner = new bondage.Runner();
  });

  it('Can run through a single node', () => {
    runner.load(linksYarnData);
    const run = runner.run('OneNode');

    expect(run.next().value).to.deep.equal(new bondage.TextResult('This is a test line'));
    expect(run.next().done).to.be.true;
  });

  it('Can start at a different node', () => {
    runner.load(linksYarnData);
    const run = runner.run('Option2');

    expect(run.next().value).to.deep.equal(new bondage.TextResult('This is Option2\'s test line'));
    expect(run.next().done).to.be.true;
  });

  it('Can run through a link to another node', () => {
    runner.load(linksYarnData);
    const run = runner.run('ThreeNodes');

    expect(run.next().value).to.deep.equal(new bondage.TextResult('This is a test line'));
    expect(run.next().value).to.deep.equal(new bondage.TextResult('This is another test line'));

    const optionResult = run.next().value;
    expect(optionResult).to.deep.equal(new bondage.OptionResult(['Option1', 'Option2']));

    optionResult.select(0);
    expect(run.next().value).to.deep.equal(new bondage.TextResult('This is Option1\'s test line'));

    expect(run.next().done).to.be.true;
  });

  it('Can run through a named link to another node', () => {
    runner.load(linksYarnData);
    const run = runner.run('NamedLink');

    expect(run.next().value).to.deep.equal(new bondage.TextResult('This is a test line'));
    expect(run.next().value).to.deep.equal(new bondage.TextResult('This is another test line'));

    const optionResult = run.next().value;
    expect(optionResult).to.deep.equal(new bondage.OptionResult(['First choice', 'Second choice']));

    optionResult.select(1);
    expect(run.next().value).to.deep.equal(new bondage.TextResult('This is Option2\'s test line'));

    expect(run.next().done).to.be.true;
  });

  it('Automatically goes to the linked node, if only one link is given', () => {
    runner.load(linksYarnData);
    const run = runner.run('OneLinkPassthrough');

    expect(run.next().value).to.deep.equal(new bondage.TextResult('First test line'));
    expect(run.next().value).to.deep.equal(new bondage.TextResult('This is Option1\'s test line'));
    expect(run.next().done).to.be.true;
  });

  it('Does not group together link and shortcut options', () => {
    runner.load(linksYarnData);
    const run = runner.run('LinkAfterShortcuts');

    expect(run.next().value).to.deep.equal(new bondage.TextResult('First test line'));

    let optionResult = run.next().value;
    expect(optionResult).to.deep.equal(new bondage.OptionResult(['Shortcut 1', 'Shortcut 2']));

    optionResult.select(1);
    expect(run.next().value).to.deep.equal(new bondage.TextResult('This is the second shortcut'));

    optionResult = run.next().value;
    expect(optionResult).to.deep.equal(new bondage.OptionResult(['First link', 'Second link']));

    optionResult.select(0);
    expect(run.next().value).to.deep.equal(new bondage.TextResult('This is Option1\'s test line'));

    expect(run.next().done).to.be.true;
  });

  it('Can run through shortcuts', () => {
    runner.load(shortcutsYarnData);
    const run = runner.run('NonNested');

    expect(run.next().value).to.deep.equal(new bondage.TextResult('This is a test line'));

    const optionResult = run.next().value;
    expect(optionResult).to.deep.equal(new bondage.OptionResult(['Option 1', 'Option 2']));

    optionResult.select(1);
    expect(run.next().value).to.deep.equal(new bondage.TextResult('This is the second option'));

    expect(run.next().value).to.deep.equal(new bondage.TextResult('This is after both options'));
    expect(run.next().done).to.be.true;
  });

  it('Can run through nested shortcuts', () => {
    runner.load(shortcutsYarnData);
    const run = runner.run('Nested');

    expect(run.next().value).to.deep.equal(new bondage.TextResult('text'));

    let optionResult = run.next().value;
    expect(optionResult).to.deep.equal(new bondage.OptionResult(['shortcut1', 'shortcut2']));

    optionResult.select(0);
    expect(run.next().value).to.deep.equal(new bondage.TextResult('Text1'));

    optionResult = run.next().value;
    expect(optionResult).to.deep.equal(new bondage.OptionResult(['nestedshortcut1', 'nestedshortcut2']));

    optionResult.select(1);
    expect(run.next().value).to.deep.equal(new bondage.TextResult('NestedText2'));

    expect(run.next().value).to.deep.equal(new bondage.TextResult('more text'));
    expect(run.next().done).to.be.true;
  });

  it('Can exclude a conditional shortcut', () => {
    runner.load(shortcutsYarnData);
    const run = runner.run('Conditional');

    expect(run.next().value).to.deep.equal(new bondage.TextResult('This is a test line'));

    const optionResult = run.next().value;
    expect(optionResult).to.deep.equal(new bondage.OptionResult(['Option 1', 'Option 3']));

    optionResult.select(1);
    expect(run.next().value).to.deep.equal(new bondage.TextResult('This is the third option'));

    expect(run.next().value).to.deep.equal(new bondage.TextResult('This is after both options'));
    expect(run.next().done).to.be.true;
  });

  it('Can evaluate a numeric assignment', () => {
    runner.load(assignmentYarnData);
    const run = runner.run('Numeric');

    expect(run.next().value).to.deep.equal(new bondage.TextResult('Test Line'));

    expect(runner.variables.get('testvar')).to.be.undefined;

    expect(run.next().value).to.deep.equal(new bondage.TextResult('Test Line After'));

    expect(runner.variables.get('testvar')).to.equal(-123.4);

    expect(run.next().done).to.be.true;
  });

  it('Can evaluate a numeric assignment with an expression', () => {
    runner.load(assignmentYarnData);
    const run = runner.run('NumericExpression');

    expect(run.next().value).to.deep.equal(new bondage.TextResult('Test Line'));

    expect(runner.variables.get('testvar')).to.be.undefined;

    expect(run.next().value).to.deep.equal(new bondage.TextResult('Test Line After'));

    expect(runner.variables.get('testvar')).to.equal(((1 + 2) * -3) + 4.3);

    expect(run.next().done).to.be.true;
  });

  it('Can evaluate an string assignment', () => {
    runner.load(assignmentYarnData);
    const run = runner.run('String');

    expect(run.next().value).to.deep.equal(new bondage.TextResult('Test Line'));

    expect(runner.variables.get('testvar')).to.be.undefined;

    expect(run.next().value).to.deep.equal(new bondage.TextResult('Test Line After'));

    expect(runner.variables.get('testvar')).to.equal('Variable String');

    expect(run.next().done).to.be.true;
  });

  it('Can evaluate a string assignment with an expression', () => {
    runner.load(assignmentYarnData);
    const run = runner.run('StringExpression');

    expect(run.next().value).to.deep.equal(new bondage.TextResult('Test Line'));

    expect(runner.variables.get('testvar')).to.be.undefined;

    expect(run.next().value).to.deep.equal(new bondage.TextResult('Test Line After'));

    expect(runner.variables.get('testvar')).to.equal('Variable String Appended');

    expect(run.next().done).to.be.true;
  });

  it('Can evaluate a boolean assignment', () => {
    runner.load(assignmentYarnData);
    const run = runner.run('Boolean');

    expect(run.next().value).to.deep.equal(new bondage.TextResult('Test Line'));

    expect(runner.variables.get('testvar')).to.be.undefined;

    expect(run.next().value).to.deep.equal(new bondage.TextResult('Test Line After'));

    expect(runner.variables.get('testvar')).to.equal(true);

    expect(run.next().done).to.be.true;
  });

  it('Can evaluate a boolean assignment with expression', () => {
    runner.load(assignmentYarnData);
    const run = runner.run('BooleanExpression');

    expect(run.next().value).to.deep.equal(new bondage.TextResult('Test Line'));

    expect(runner.variables.get('testvar')).to.be.undefined;

    expect(run.next().value).to.deep.equal(new bondage.TextResult('Test Line After'));

    expect(runner.variables.get('testvar')).to.equal(true);

    expect(run.next().done).to.be.true;
  });

  it('Can evaluate an assignment from one variable to another', () => {
    runner.load(assignmentYarnData);
    const run = runner.run('Variable');

    expect(run.next().value).to.deep.equal(new bondage.TextResult('Test Line'));

    expect(runner.variables.get('firstvar')).to.be.undefined;
    expect(runner.variables.get('secondvar')).to.be.undefined;

    expect(run.next().value).to.deep.equal(new bondage.TextResult('Test Line After'));

    expect(runner.variables.get('secondvar')).to.equal('First variable string');

    expect(run.next().done).to.be.true;
  });

  it('Can evaluate an assignment from one variable to another via an expression', () => {
    runner.load(assignmentYarnData);
    const run = runner.run('VariableExpression');

    expect(run.next().value).to.deep.equal(new bondage.TextResult('Test Line'));

    expect(runner.variables.get('firstvar')).to.be.undefined;
    expect(runner.variables.get('secondvar')).to.be.undefined;

    expect(run.next().value).to.deep.equal(new bondage.TextResult('Test Line After'));

    expect(runner.variables.get('secondvar')).to.equal(-4.3 + 100);

    expect(run.next().done).to.be.true;
  });

  it('Can handle an if conditional', () => {
    runner.load(conditionalYarnData);
    const run = runner.run('BasicIf');

    expect(run.next().value).to.deep.equal(new bondage.TextResult('Text before'));
    expect(run.next().value).to.deep.equal(new bondage.TextResult('Inside if'));
    expect(run.next().value).to.deep.equal(new bondage.TextResult('Text after'));

    expect(run.next().done).to.be.true;
  });

  it('Can handle an if else conditional', () => {
    runner.load(conditionalYarnData);
    const run = runner.run('BasicIfElse');

    expect(run.next().value).to.deep.equal(new bondage.TextResult('Text before'));
    expect(run.next().value).to.deep.equal(new bondage.TextResult('Inside else'));
    expect(run.next().value).to.deep.equal(new bondage.TextResult('Text after'));

    expect(run.next().done).to.be.true;
  });

  it('Can handle an if else if conditional', () => {
    runner.load(conditionalYarnData);
    const run = runner.run('BasicIfElseIf');

    expect(run.next().value).to.deep.equal(new bondage.TextResult('Text before'));
    expect(run.next().value).to.deep.equal(new bondage.TextResult('Inside elseif'));
    expect(run.next().value).to.deep.equal(new bondage.TextResult('Text after'));

    expect(run.next().done).to.be.true;
  });

  it('Can handle an if else if else conditional', () => {
    runner.load(conditionalYarnData);
    const run = runner.run('BasicIfElseIfElse');

    expect(run.next().value).to.deep.equal(new bondage.TextResult('Text before'));
    expect(run.next().value).to.deep.equal(new bondage.TextResult('Inside else'));
    expect(run.next().value).to.deep.equal(new bondage.TextResult('Text after'));

    expect(run.next().done).to.be.true;
  });

  it('Halts when given the <<stop>> command', () => {
    runner.load(commandAndFunctionYarnData);
    const run = runner.run('StopCommand');

    expect(run.next().value).to.deep.equal(new bondage.TextResult('First line'));
    expect(run.next().done).to.be.true;
  });

  it('Returns a command to the user', () => {
    runner.load(commandAndFunctionYarnData);
    const run = runner.run('BasicCommands');

    let lastCommand = '';
    runner.setCommandHandler((command) => {
      lastCommand = command;
    });

    expect(run.next().value).to.deep.equal(new bondage.TextResult('text in between commands'));
    expect(lastCommand).to.equal('command1');

    expect(run.next().done).to.be.true;
    expect(lastCommand).to.equal('command with space');
  });

  it('Evaluates a function and uses it in a conditional', () => {
    runner.load(commandAndFunctionYarnData);
    const run = runner.run('FunctionConditional');

    runner.registerFunction('testfunc', (args) => {
      if (args[0] === 'firstarg') {
        if (args[1] === 'secondarg') {
          // Test returning true
          return true;
        }
        // Test returning false
        return false;
      }

      throw new Error(`Args ${args} were not expected in testfunc`);
    });

    expect(run.next().value).to.deep.equal(new bondage.TextResult('First line'));
    expect(run.next().value).to.deep.equal(new bondage.TextResult('This should show'));
    expect(run.next().value).to.deep.equal(new bondage.TextResult('After both'));

    expect(run.next().done).to.be.true;
  });

  it('Returns a command to the user', () => {
    runner.load(commandAndFunctionYarnData);
    const run = runner.run('VisitedFunctionStart');

    expect(run.next().value).to.deep.equal(new bondage.TextResult('Hello'));
    expect(run.next().value).to.deep.equal(new bondage.TextResult('you have visited VisitedFunctionStart!'));

    expect(run.next().done).to.be.true;
  });
});
<commit_msg>Fix mislabeled test<commit_after>/* eslint no-unused-expressions: "off" */
/* eslint-env mocha */

'use strict';

const fs = require('fs');
const chai = require('chai');
const bondage = require('../src/bondage.js');

const expect = chai.expect;

describe('Dialogue', () => {
  let linksYarnData;
  let shortcutsYarnData;
  let assignmentYarnData;
  let conditionalYarnData;
  let commandAndFunctionYarnData;

  let runner;

  before(() => {
    linksYarnData = JSON.parse(fs.readFileSync('./tests/yarn_files/links.json'));
    shortcutsYarnData = JSON.parse(fs.readFileSync('./tests/yarn_files/shortcuts.json'));
    assignmentYarnData = JSON.parse(fs.readFileSync('./tests/yarn_files/assignment.json'));
    conditionalYarnData = JSON.parse(fs.readFileSync('./tests/yarn_files/conditions.json'));
    commandAndFunctionYarnData = JSON.parse(fs.readFileSync('./tests/yarn_files/commandsandfunctions.json'));
  });

  beforeEach(() => {
    runner = new bondage.Runner();
  });

  it('Can run through a single node', () => {
    runner.load(linksYarnData);
    const run = runner.run('OneNode');

    expect(run.next().value).to.deep.equal(new bondage.TextResult('This is a test line'));
    expect(run.next().done).to.be.true;
  });

  it('Can start at a different node', () => {
    runner.load(linksYarnData);
    const run = runner.run('Option2');

    expect(run.next().value).to.deep.equal(new bondage.TextResult('This is Option2\'s test line'));
    expect(run.next().done).to.be.true;
  });

  it('Can run through a link to another node', () => {
    runner.load(linksYarnData);
    const run = runner.run('ThreeNodes');

    expect(run.next().value).to.deep.equal(new bondage.TextResult('This is a test line'));
    expect(run.next().value).to.deep.equal(new bondage.TextResult('This is another test line'));

    const optionResult = run.next().value;
    expect(optionResult).to.deep.equal(new bondage.OptionResult(['Option1', 'Option2']));

    optionResult.select(0);
    expect(run.next().value).to.deep.equal(new bondage.TextResult('This is Option1\'s test line'));

    expect(run.next().done).to.be.true;
  });

  it('Can run through a named link to another node', () => {
    runner.load(linksYarnData);
    const run = runner.run('NamedLink');

    expect(run.next().value).to.deep.equal(new bondage.TextResult('This is a test line'));
    expect(run.next().value).to.deep.equal(new bondage.TextResult('This is another test line'));

    const optionResult = run.next().value;
    expect(optionResult).to.deep.equal(new bondage.OptionResult(['First choice', 'Second choice']));

    optionResult.select(1);
    expect(run.next().value).to.deep.equal(new bondage.TextResult('This is Option2\'s test line'));

    expect(run.next().done).to.be.true;
  });

  it('Automatically goes to the linked node, if only one link is given', () => {
    runner.load(linksYarnData);
    const run = runner.run('OneLinkPassthrough');

    expect(run.next().value).to.deep.equal(new bondage.TextResult('First test line'));
    expect(run.next().value).to.deep.equal(new bondage.TextResult('This is Option1\'s test line'));
    expect(run.next().done).to.be.true;
  });

  it('Does not group together link and shortcut options', () => {
    runner.load(linksYarnData);
    const run = runner.run('LinkAfterShortcuts');

    expect(run.next().value).to.deep.equal(new bondage.TextResult('First test line'));

    let optionResult = run.next().value;
    expect(optionResult).to.deep.equal(new bondage.OptionResult(['Shortcut 1', 'Shortcut 2']));

    optionResult.select(1);
    expect(run.next().value).to.deep.equal(new bondage.TextResult('This is the second shortcut'));

    optionResult = run.next().value;
    expect(optionResult).to.deep.equal(new bondage.OptionResult(['First link', 'Second link']));

    optionResult.select(0);
    expect(run.next().value).to.deep.equal(new bondage.TextResult('This is Option1\'s test line'));

    expect(run.next().done).to.be.true;
  });

  it('Can run through shortcuts', () => {
    runner.load(shortcutsYarnData);
    const run = runner.run('NonNested');

    expect(run.next().value).to.deep.equal(new bondage.TextResult('This is a test line'));

    const optionResult = run.next().value;
    expect(optionResult).to.deep.equal(new bondage.OptionResult(['Option 1', 'Option 2']));

    optionResult.select(1);
    expect(run.next().value).to.deep.equal(new bondage.TextResult('This is the second option'));

    expect(run.next().value).to.deep.equal(new bondage.TextResult('This is after both options'));
    expect(run.next().done).to.be.true;
  });

  it('Can run through nested shortcuts', () => {
    runner.load(shortcutsYarnData);
    const run = runner.run('Nested');

    expect(run.next().value).to.deep.equal(new bondage.TextResult('text'));

    let optionResult = run.next().value;
    expect(optionResult).to.deep.equal(new bondage.OptionResult(['shortcut1', 'shortcut2']));

    optionResult.select(0);
    expect(run.next().value).to.deep.equal(new bondage.TextResult('Text1'));

    optionResult = run.next().value;
    expect(optionResult).to.deep.equal(new bondage.OptionResult(['nestedshortcut1', 'nestedshortcut2']));

    optionResult.select(1);
    expect(run.next().value).to.deep.equal(new bondage.TextResult('NestedText2'));

    expect(run.next().value).to.deep.equal(new bondage.TextResult('more text'));
    expect(run.next().done).to.be.true;
  });

  it('Can exclude a conditional shortcut', () => {
    runner.load(shortcutsYarnData);
    const run = runner.run('Conditional');

    expect(run.next().value).to.deep.equal(new bondage.TextResult('This is a test line'));

    const optionResult = run.next().value;
    expect(optionResult).to.deep.equal(new bondage.OptionResult(['Option 1', 'Option 3']));

    optionResult.select(1);
    expect(run.next().value).to.deep.equal(new bondage.TextResult('This is the third option'));

    expect(run.next().value).to.deep.equal(new bondage.TextResult('This is after both options'));
    expect(run.next().done).to.be.true;
  });

  it('Can evaluate a numeric assignment', () => {
    runner.load(assignmentYarnData);
    const run = runner.run('Numeric');

    expect(run.next().value).to.deep.equal(new bondage.TextResult('Test Line'));

    expect(runner.variables.get('testvar')).to.be.undefined;

    expect(run.next().value).to.deep.equal(new bondage.TextResult('Test Line After'));

    expect(runner.variables.get('testvar')).to.equal(-123.4);

    expect(run.next().done).to.be.true;
  });

  it('Can evaluate a numeric assignment with an expression', () => {
    runner.load(assignmentYarnData);
    const run = runner.run('NumericExpression');

    expect(run.next().value).to.deep.equal(new bondage.TextResult('Test Line'));

    expect(runner.variables.get('testvar')).to.be.undefined;

    expect(run.next().value).to.deep.equal(new bondage.TextResult('Test Line After'));

    expect(runner.variables.get('testvar')).to.equal(((1 + 2) * -3) + 4.3);

    expect(run.next().done).to.be.true;
  });

  it('Can evaluate an string assignment', () => {
    runner.load(assignmentYarnData);
    const run = runner.run('String');

    expect(run.next().value).to.deep.equal(new bondage.TextResult('Test Line'));

    expect(runner.variables.get('testvar')).to.be.undefined;

    expect(run.next().value).to.deep.equal(new bondage.TextResult('Test Line After'));

    expect(runner.variables.get('testvar')).to.equal('Variable String');

    expect(run.next().done).to.be.true;
  });

  it('Can evaluate a string assignment with an expression', () => {
    runner.load(assignmentYarnData);
    const run = runner.run('StringExpression');

    expect(run.next().value).to.deep.equal(new bondage.TextResult('Test Line'));

    expect(runner.variables.get('testvar')).to.be.undefined;

    expect(run.next().value).to.deep.equal(new bondage.TextResult('Test Line After'));

    expect(runner.variables.get('testvar')).to.equal('Variable String Appended');

    expect(run.next().done).to.be.true;
  });

  it('Can evaluate a boolean assignment', () => {
    runner.load(assignmentYarnData);
    const run = runner.run('Boolean');

    expect(run.next().value).to.deep.equal(new bondage.TextResult('Test Line'));

    expect(runner.variables.get('testvar')).to.be.undefined;

    expect(run.next().value).to.deep.equal(new bondage.TextResult('Test Line After'));

    expect(runner.variables.get('testvar')).to.equal(true);

    expect(run.next().done).to.be.true;
  });

  it('Can evaluate a boolean assignment with expression', () => {
    runner.load(assignmentYarnData);
    const run = runner.run('BooleanExpression');

    expect(run.next().value).to.deep.equal(new bondage.TextResult('Test Line'));

    expect(runner.variables.get('testvar')).to.be.undefined;

    expect(run.next().value).to.deep.equal(new bondage.TextResult('Test Line After'));

    expect(runner.variables.get('testvar')).to.equal(true);

    expect(run.next().done).to.be.true;
  });

  it('Can evaluate an assignment from one variable to another', () => {
    runner.load(assignmentYarnData);
    const run = runner.run('Variable');

    expect(run.next().value).to.deep.equal(new bondage.TextResult('Test Line'));

    expect(runner.variables.get('firstvar')).to.be.undefined;
    expect(runner.variables.get('secondvar')).to.be.undefined;

    expect(run.next().value).to.deep.equal(new bondage.TextResult('Test Line After'));

    expect(runner.variables.get('secondvar')).to.equal('First variable string');

    expect(run.next().done).to.be.true;
  });

  it('Can evaluate an assignment from one variable to another via an expression', () => {
    runner.load(assignmentYarnData);
    const run = runner.run('VariableExpression');

    expect(run.next().value).to.deep.equal(new bondage.TextResult('Test Line'));

    expect(runner.variables.get('firstvar')).to.be.undefined;
    expect(runner.variables.get('secondvar')).to.be.undefined;

    expect(run.next().value).to.deep.equal(new bondage.TextResult('Test Line After'));

    expect(runner.variables.get('secondvar')).to.equal(-4.3 + 100);

    expect(run.next().done).to.be.true;
  });

  it('Can handle an if conditional', () => {
    runner.load(conditionalYarnData);
    const run = runner.run('BasicIf');

    expect(run.next().value).to.deep.equal(new bondage.TextResult('Text before'));
    expect(run.next().value).to.deep.equal(new bondage.TextResult('Inside if'));
    expect(run.next().value).to.deep.equal(new bondage.TextResult('Text after'));

    expect(run.next().done).to.be.true;
  });

  it('Can handle an if else conditional', () => {
    runner.load(conditionalYarnData);
    const run = runner.run('BasicIfElse');

    expect(run.next().value).to.deep.equal(new bondage.TextResult('Text before'));
    expect(run.next().value).to.deep.equal(new bondage.TextResult('Inside else'));
    expect(run.next().value).to.deep.equal(new bondage.TextResult('Text after'));

    expect(run.next().done).to.be.true;
  });

  it('Can handle an if else if conditional', () => {
    runner.load(conditionalYarnData);
    const run = runner.run('BasicIfElseIf');

    expect(run.next().value).to.deep.equal(new bondage.TextResult('Text before'));
    expect(run.next().value).to.deep.equal(new bondage.TextResult('Inside elseif'));
    expect(run.next().value).to.deep.equal(new bondage.TextResult('Text after'));

    expect(run.next().done).to.be.true;
  });

  it('Can handle an if else if else conditional', () => {
    runner.load(conditionalYarnData);
    const run = runner.run('BasicIfElseIfElse');

    expect(run.next().value).to.deep.equal(new bondage.TextResult('Text before'));
    expect(run.next().value).to.deep.equal(new bondage.TextResult('Inside else'));
    expect(run.next().value).to.deep.equal(new bondage.TextResult('Text after'));

    expect(run.next().done).to.be.true;
  });

  it('Halts when given the <<stop>> command', () => {
    runner.load(commandAndFunctionYarnData);
    const run = runner.run('StopCommand');

    expect(run.next().value).to.deep.equal(new bondage.TextResult('First line'));
    expect(run.next().done).to.be.true;
  });

  it('Returns a command to the user', () => {
    runner.load(commandAndFunctionYarnData);
    const run = runner.run('BasicCommands');

    let lastCommand = '';
    runner.setCommandHandler((command) => {
      lastCommand = command;
    });

    expect(run.next().value).to.deep.equal(new bondage.TextResult('text in between commands'));
    expect(lastCommand).to.equal('command1');

    expect(run.next().done).to.be.true;
    expect(lastCommand).to.equal('command with space');
  });

  it('Evaluates a function and uses it in a conditional', () => {
    runner.load(commandAndFunctionYarnData);
    const run = runner.run('FunctionConditional');

    runner.registerFunction('testfunc', (args) => {
      if (args[0] === 'firstarg') {
        if (args[1] === 'secondarg') {
          // Test returning true
          return true;
        }
        // Test returning false
        return false;
      }

      throw new Error(`Args ${args} were not expected in testfunc`);
    });

    expect(run.next().value).to.deep.equal(new bondage.TextResult('First line'));
    expect(run.next().value).to.deep.equal(new bondage.TextResult('This should show'));
    expect(run.next().value).to.deep.equal(new bondage.TextResult('After both'));

    expect(run.next().done).to.be.true;
  });

  it('Correctly defines the built-in visited() function', () => {
    runner.load(commandAndFunctionYarnData);
    const run = runner.run('VisitedFunctionStart');

    expect(run.next().value).to.deep.equal(new bondage.TextResult('Hello'));
    expect(run.next().value).to.deep.equal(new bondage.TextResult('you have visited VisitedFunctionStart!'));

    expect(run.next().done).to.be.true;
  });
});
<|endoftext|>


<commit_before>if (typeof define !== 'function') {
  var define = require('amdefine')(module);
}
define(function (require, exports, module) {
  'use strict';

  var EventEmitter = require('./events').EventEmitter;
  var Parser = require('./parser').Parser;
  var Compiler = require('./compiler').Compiler;
  var io = require('./platform/io');
  var getPluralRule = require('./plurals').getPluralRule;

  function Resource(id, parser) {
    var self = this;

    this.id = id;
    this.resources = [];
    this.source = null;
    this.ast = null;

    this.build = build;

    function build(callback, sync) {
      if (self.source) {
        parse();
      } else if (self.ast) {
        callback();
      } else {
        io.load(self.id, parse, sync);
      }

      function parse(err, text) {
        if (err) {
          self.ast = {
            type: 'WebL10n',
            body: {}
          };
          return callback(err);
        } else if (text === undefined) {
          self.ast = parser.parse(self.source);
        } else {
          if (/\.json/.test(self.id)) {
            // JSON is guaranteed to be an AST
            self.ast = JSON.parse(text);
          } else {
            self.source = text;
            self.ast = parser.parse(self.source);
          }
        }
        callback();
      }
    }
  }

  function Locale(id, parser, compiler, emitter) {
    this.id = id;
    this.resources = [];
    this.entries = null;
    this.ast = {
      type: 'WebL10n',
      body: {}
    };
    this.isReady = false;

    this.build = build;
    this.getEntry = getEntry;
    this.hasResource = hasResource;

    var self = this;

    function build(callback) {
      if (!callback) {
        var sync = true;
      }

      var resourcesToBuild = self.resources.length;
      if (resourcesToBuild === 0) {
        throw new ContextError('Locale has no resources');
      }

      var resourcesWithErrors = 0;
      self.resources.forEach(function(res) {
        res.build(resourceBuilt, sync);
      });

      function resourceBuilt(err) {
        if (err) {
          resourcesWithErrors++;
          emitter.emit(err instanceof ContextError ? 'error' : 'warning', err);
        }
        resourcesToBuild--;
        if (resourcesToBuild == 0) {
          if (resourcesWithErrors == self.resources.length) {
            // XXX Bug 908780 - Decide what to do when all resources in
            // a locale are missing or broken
            // https://bugzilla.mozilla.org/show_bug.cgi?id=908780
            emitter.emit('error',
                         new ContextError('Locale has no valid resources'));
          }
          flatten();
        }
      }

      function flatten() {
        self.ast.body = self.resources.reduce(function(prev, curr) {
          if (!curr.ast) {
            return prev;
          }
          for (var key in curr.ast.body) {
            if (curr.ast.body.hasOwnProperty(key)) {
              prev[key] = curr.ast.body[key];
            }
          }
          return prev;
        }, self.ast.body);
        compile();
      }

      function compile() {
        self.entries = compiler.compile(self.ast);
        self.isReady = true;
        if (callback) {
          callback();
        }
      }
    }

    function getEntry(id) {
      if (this.entries.hasOwnProperty(id)) {
        return this.entries[id];
      }
      return undefined;
    }

    function hasResource(uri) {
      return this.resources.some(function(res) {
        return res.id === uri;
      });
    }
  }

  function Context(id) {

    this.id = id;

    this.registerLocales = registerLocales;
    this.requestLocales = requestLocales;
    this.addDictionary = addDictionary;
    this.linkResource = linkResource;

    this.get = get;
    this.getEntity = getEntity;
    this.getSource = getSource;
    this.getSources = getSources;
    this.ready = ready;
    this.once = once;

    this.addEventListener = addEventListener;
    this.removeEventListener = removeEventListener;

    Object.defineProperty(this, 'supportedLocales', {
      get: function() { return _fallbackChain.slice(); },
      enumerable: true
    });

    // registered and available languages
    var _default = 'i-default';
    var _registered = [_default];
    var _requested = [];
    var _fallbackChain = [];
    // Locale objects corresponding to the registered languages
    var _locales = {};

    // URLs or text of resources (with information about the type) added via
    // linkResource
    var _reslinks = [];

    var _isReady = false;
    var _isFrozen = false;

    Object.defineProperty(this, 'isReady', {
      get: function() { return _isReady; },
      enumerable: true
    });

    var _emitter = new EventEmitter();
    var _parser = new Parser();
    var _compiler = new Compiler();

    _parser.addEventListener('error', error);
    _compiler.addEventListener('error', warn);

    var self = this;

    function get(id, data) {
      if (!_isReady) {
        throw new ContextError('Context not ready');
      }
      return getFromLocale.call(self, 0, id, data).value;
    }

    function getEntity(id, data) {
      if (!_isReady) {
        throw new ContextError('Context not ready');
      }
      return getFromLocale.call(self, 0, id, data);
    }

    function getSource(id) {
      if (!_isReady) {
        throw new ContextError('Context not ready');
      }

      var current = 0;
      var locale = getLocale(_fallbackChain[current]);
      // if the requested id doesn't exist in the first locale, fall back
      while (!locale.getEntry(id)) {
        warn(new TranslationError('Not found', id, _fallbackChain, locale));
        var nextLocale = _fallbackChain[++current];
        if (!nextLocale) {
          return null;
        }

        locale = getLocale(nextLocale);
        if (!locale.isReady) {
          locale.build();
        }
      }
      return locale.ast.body[id];
    }

    function getSources() {
      if (!_isReady) {
        throw new ContextError('Context not ready');
      }
      var defLoc = getLocale(_default);
      if (!defLoc.isReady) {
        defLoc.build();
      }
      var body = {};
      for (var id in defLoc.entries) {
        if (!defLoc.entries.hasOwnProperty(id)) {
          continue;
        }
        // if it's an Entity, add it
        if (defLoc.entries[id].get) {
          var source = getSource(id);
          if (source) {
            body[id] = source;
          }
        }
      }
      return body;
    }

    function ready(callback) {
      if (_isReady) {
        setTimeout(callback);
      }
      addEventListener('ready', callback);
    }

    function once(callback) {
      if (_isReady) {
        setTimeout(callback);
      }
      var callAndRemove = function callAndRemove() {
        removeEventListener('ready', callAndRemove);
        callback();
      };
      addEventListener('ready', callAndRemove);
    }

    function getFromLocale(cur, id, data, prevSource) {
      var loc = _fallbackChain[cur];
      if (!loc) {
        error(new RuntimeError('Unable to get translation', id,
                               _fallbackChain));
        // imitate the return value of Compiler.Entity.get
        return {
          value: prevSource ? prevSource.source : id,
          attributes: {},
          globals: {},
          locale: prevSource ? prevSource.loc : null
        };
      }

      var locale = getLocale(loc);
      if (!locale.isReady) {
        // build without a callback, synchronously
        locale.build(null);
      }

      var entry = locale.getEntry(id);

      // if the entry is missing, just go to the next locale immediately
      if (entry === undefined) {
        warn(new TranslationError('Not found', id, _fallbackChain, locale));
        return getFromLocale.call(this, cur + 1, id, data, prevSource);
      }

      // otherwise, try to get the value of the entry
      try {
        var value = entry.get(data);
      } catch (e) {
        if (e instanceof Compiler.RuntimeError) {
          error(new TranslationError(e.message, id, _fallbackChain, locale));
          if (e instanceof Compiler.ValueError) {
            // salvage the source string which the compiler wasn't able to
            // evaluate completely;  this is still better than returning the
            // identifer;  prefer a source string from locales earlier in the
            // fallback chain, if available
            var source = prevSource || { source: e.source, loc: locale.id };
            return getFromLocale.call(this, cur + 1, id, data, source);
          }
          return getFromLocale.call(this, cur + 1, id, data, prevSource);
        } else {
          throw error(e);
        }
      }
      value.locale = locale.id;
      return value;
    }

    function add(text, locale) {
      var res = new Resource(null, _parser);
      res.source = text;
      locale.resources.push(res);
    }

    function addDictionary(scriptNode, loc) {
      if (_isFrozen) {
        throw new ContextError('Context is frozen');
      }
      _reslinks.push(['dict', scriptNode, loc]);
    }

    function addJSON(scriptNode, locale) {
      var res = new Resource(null);
      res.ast = JSON.parse(scriptNode.innerHTML);
      locale.resources.push(res);
    }

    function linkResource(uri) {
      if (_isFrozen) {
        throw new ContextError('Context is frozen');
      }
      _reslinks.push([typeof uri === 'function' ? 'template' : 'uri', uri]);
    }

    function link(uri, locale) {
      if (!locale.hasResource(uri)) {
        var res = new Resource(uri, _parser);
        locale.resources.push(res);
      }
    }

    function registerLocales(defaultLocale, availableLocales) {
      if (_isFrozen) {
        throw new ContextError('Context is frozen');
      }

      if (defaultLocale === undefined) {
        return;
      }

      _default = defaultLocale;
      _registered = [];

      if (!availableLocales) {
        availableLocales = [];
      }
      availableLocales.push(defaultLocale);

      // uniquify `available` into `_registered`
      availableLocales.forEach(function l10n_ctx_addlocale(locale) {
        if (typeof locale !== 'string') {
          throw new ContextError('Language codes must be strings');
        }
        if (_registered.indexOf(locale) === -1) {
          _registered.push(locale);
        }
      });
    }

    function negotiate(available, requested, defaultLocale) {
      if (available.indexOf(requested[0]) === -1 ||
          requested[0] === defaultLocale) {
        return [defaultLocale];
      } else {
        return [requested[0], defaultLocale];
      }
    }

    function getLocale(code) {
      if (_locales[code]) {
        return _locales[code];
      }

      var locale = new Locale(code, _parser, _compiler, _emitter);
      _locales[code] = locale;
      // populate the locale with resources
      for (var j = 0; j < _reslinks.length; j++) {
        var res = _reslinks[j];
        if (res[0] === 'dict') {
          // a JSON resource added via addDictionary(HTMLScriptElement)
          // only add if no locale was specified or if the locale specified
          // matches the locale being created here
          if (res[2] === undefined || res[2] === locale.id) {
            addJSON(res[1], locale);
          }
        } else if (res[0] === 'uri') {
          // a resource added via linkResource(String)
          link(res[1], locale);
        } else {
          // a resource added via linkResource(Function);  the function
          // passed is a URL template and it takes the current locale's code
          // as an argument
          link(res[1](locale.id), locale);
        }
      }
      locale.ast.body['plural'] = {
        type: 'Macro',
        args: [{
          type: 'Identifier',
          name: 'n'
        }],
        expression: getPluralRule(code)
      };
      return locale;
    }

    function requestLocales() {
      if (_isFrozen && !_isReady) {
        throw new ContextError('Context not ready');
      }

      if (_reslinks.length == 0) {
        warn(new ContextError('Context has no resources; not freezing'));
        return;
      }

      _isFrozen = true;
      _requested = Array.prototype.slice.call(arguments);

      if (_requested.length) {
        _requested.forEach(function l10n_throwError(locale) {
          if (typeof locale !== 'string') {
            throw new ContextError('Language codes must be strings');
          }
        });
      }

      var fallbackChain = negotiate(_registered, _requested, _default);
      // if the negotiator returned something, freeze synchronously
      if (fallbackChain) {
        freeze(fallbackChain);
      }
    }

    function freeze(fallbackChain) {
      _fallbackChain = fallbackChain;
      var locale = getLocale(_fallbackChain[0]);
      if (locale.isReady) {
        setReady();
      } else {
        locale.build(setReady);
      }
    }

    function setReady() {
      _isReady = true;
      _emitter.emit('ready');
    }

    function addEventListener(type, listener) {
      _emitter.addEventListener(type, listener);
    }

    function removeEventListener(type, listener) {
      _emitter.removeEventListener(type, listener);
    }

    function warn(e) {
      _emitter.emit('warning', e);
      return e;
    }

    function error(e) {
      _emitter.emit('error', e);
      return e;
    }
  }

  Context.Error = ContextError;
  Context.RuntimeError = RuntimeError;
  Context.TranslationError = TranslationError;

  function ContextError(message) {
    this.name = 'ContextError';
    this.message = message;
  }
  ContextError.prototype = Object.create(Error.prototype);
  ContextError.prototype.constructor = ContextError;

  function RuntimeError(message, id, supported) {
    ContextError.call(this, message);
    this.name = 'RuntimeError';
    this.entity = id;
    this.supportedLocales = supported.slice();
    this.message = id + ': ' + message + '; tried ' + supported.join(', ');
  }
  RuntimeError.prototype = Object.create(ContextError.prototype);
  RuntimeError.prototype.constructor = RuntimeError;

  function TranslationError(message, id, supported, locale) {
    RuntimeError.call(this, message, id, supported);
    this.name = 'TranslationError';
    this.locale = locale.id;
    this.message = '[' + this.locale + '] ' + id + ': ' + message;
  }
  TranslationError.prototype = Object.create(RuntimeError.prototype);
  TranslationError.prototype.constructor = TranslationError;

  exports.Context = Context;
  exports.Locale = Locale;
  exports.Resource = Resource;

});
<commit_msg>Bring addResource back so that tests pass<commit_after>if (typeof define !== 'function') {
  var define = require('amdefine')(module);
}
define(function (require, exports, module) {
  'use strict';

  var EventEmitter = require('./events').EventEmitter;
  var Parser = require('./parser').Parser;
  var Compiler = require('./compiler').Compiler;
  var io = require('./platform/io');
  var getPluralRule = require('./plurals').getPluralRule;

  function Resource(id, parser) {
    var self = this;

    this.id = id;
    this.resources = [];
    this.source = null;
    this.ast = null;

    this.build = build;

    function build(callback, sync) {
      if (self.source) {
        parse();
      } else if (self.ast) {
        callback();
      } else {
        io.load(self.id, parse, sync);
      }

      function parse(err, text) {
        if (err) {
          self.ast = {
            type: 'WebL10n',
            body: {}
          };
          return callback(err);
        } else if (text === undefined) {
          self.ast = parser.parse(self.source);
        } else {
          if (/\.json/.test(self.id)) {
            // JSON is guaranteed to be an AST
            self.ast = JSON.parse(text);
          } else {
            self.source = text;
            self.ast = parser.parse(self.source);
          }
        }
        callback();
      }
    }
  }

  function Locale(id, parser, compiler, emitter) {
    this.id = id;
    this.resources = [];
    this.entries = null;
    this.ast = {
      type: 'WebL10n',
      body: {}
    };
    this.isReady = false;

    this.build = build;
    this.getEntry = getEntry;
    this.hasResource = hasResource;

    var self = this;

    function build(callback) {
      if (!callback) {
        var sync = true;
      }

      var resourcesToBuild = self.resources.length;
      if (resourcesToBuild === 0) {
        throw new ContextError('Locale has no resources');
      }

      var resourcesWithErrors = 0;
      self.resources.forEach(function(res) {
        res.build(resourceBuilt, sync);
      });

      function resourceBuilt(err) {
        if (err) {
          resourcesWithErrors++;
          emitter.emit(err instanceof ContextError ? 'error' : 'warning', err);
        }
        resourcesToBuild--;
        if (resourcesToBuild == 0) {
          if (resourcesWithErrors == self.resources.length) {
            // XXX Bug 908780 - Decide what to do when all resources in
            // a locale are missing or broken
            // https://bugzilla.mozilla.org/show_bug.cgi?id=908780
            emitter.emit('error',
                         new ContextError('Locale has no valid resources'));
          }
          flatten();
        }
      }

      function flatten() {
        self.ast.body = self.resources.reduce(function(prev, curr) {
          if (!curr.ast) {
            return prev;
          }
          for (var key in curr.ast.body) {
            if (curr.ast.body.hasOwnProperty(key)) {
              prev[key] = curr.ast.body[key];
            }
          }
          return prev;
        }, self.ast.body);
        compile();
      }

      function compile() {
        self.entries = compiler.compile(self.ast);
        self.isReady = true;
        if (callback) {
          callback();
        }
      }
    }

    function getEntry(id) {
      if (this.entries.hasOwnProperty(id)) {
        return this.entries[id];
      }
      return undefined;
    }

    function hasResource(uri) {
      return this.resources.some(function(res) {
        return res.id === uri;
      });
    }
  }

  function Context(id) {

    this.id = id;

    this.registerLocales = registerLocales;
    this.requestLocales = requestLocales;
    this.addDictionary = addDictionary;
    this.addResource = addResource;
    this.linkResource = linkResource;

    this.get = get;
    this.getEntity = getEntity;
    this.getSource = getSource;
    this.getSources = getSources;
    this.ready = ready;
    this.once = once;

    this.addEventListener = addEventListener;
    this.removeEventListener = removeEventListener;

    Object.defineProperty(this, 'supportedLocales', {
      get: function() { return _fallbackChain.slice(); },
      enumerable: true
    });

    // registered and available languages
    var _default = 'i-default';
    var _registered = [_default];
    var _requested = [];
    var _fallbackChain = [];
    // Locale objects corresponding to the registered languages
    var _locales = {};

    // URLs or text of resources (with information about the type) added via
    // linkResource
    var _reslinks = [];

    var _isReady = false;
    var _isFrozen = false;

    Object.defineProperty(this, 'isReady', {
      get: function() { return _isReady; },
      enumerable: true
    });

    var _emitter = new EventEmitter();
    var _parser = new Parser();
    var _compiler = new Compiler();

    _parser.addEventListener('error', error);
    _compiler.addEventListener('error', warn);

    var self = this;

    function get(id, data) {
      if (!_isReady) {
        throw new ContextError('Context not ready');
      }
      return getFromLocale.call(self, 0, id, data).value;
    }

    function getEntity(id, data) {
      if (!_isReady) {
        throw new ContextError('Context not ready');
      }
      return getFromLocale.call(self, 0, id, data);
    }

    function getSource(id) {
      if (!_isReady) {
        throw new ContextError('Context not ready');
      }

      var current = 0;
      var locale = getLocale(_fallbackChain[current]);
      // if the requested id doesn't exist in the first locale, fall back
      while (!locale.getEntry(id)) {
        warn(new TranslationError('Not found', id, _fallbackChain, locale));
        var nextLocale = _fallbackChain[++current];
        if (!nextLocale) {
          return null;
        }

        locale = getLocale(nextLocale);
        if (!locale.isReady) {
          locale.build();
        }
      }
      return locale.ast.body[id];
    }

    function getSources() {
      if (!_isReady) {
        throw new ContextError('Context not ready');
      }
      var defLoc = getLocale(_default);
      if (!defLoc.isReady) {
        defLoc.build();
      }
      var body = {};
      for (var id in defLoc.entries) {
        if (!defLoc.entries.hasOwnProperty(id)) {
          continue;
        }
        // if it's an Entity, add it
        if (defLoc.entries[id].get) {
          var source = getSource(id);
          if (source) {
            body[id] = source;
          }
        }
      }
      return body;
    }

    function ready(callback) {
      if (_isReady) {
        setTimeout(callback);
      }
      addEventListener('ready', callback);
    }

    function once(callback) {
      if (_isReady) {
        setTimeout(callback);
      }
      var callAndRemove = function callAndRemove() {
        removeEventListener('ready', callAndRemove);
        callback();
      };
      addEventListener('ready', callAndRemove);
    }

    function getFromLocale(cur, id, data, prevSource) {
      var loc = _fallbackChain[cur];
      if (!loc) {
        error(new RuntimeError('Unable to get translation', id,
                               _fallbackChain));
        // imitate the return value of Compiler.Entity.get
        return {
          value: prevSource ? prevSource.source : id,
          attributes: {},
          globals: {},
          locale: prevSource ? prevSource.loc : null
        };
      }

      var locale = getLocale(loc);
      if (!locale.isReady) {
        // build without a callback, synchronously
        locale.build(null);
      }

      var entry = locale.getEntry(id);

      // if the entry is missing, just go to the next locale immediately
      if (entry === undefined) {
        warn(new TranslationError('Not found', id, _fallbackChain, locale));
        return getFromLocale.call(this, cur + 1, id, data, prevSource);
      }

      // otherwise, try to get the value of the entry
      try {
        var value = entry.get(data);
      } catch (e) {
        if (e instanceof Compiler.RuntimeError) {
          error(new TranslationError(e.message, id, _fallbackChain, locale));
          if (e instanceof Compiler.ValueError) {
            // salvage the source string which the compiler wasn't able to
            // evaluate completely;  this is still better than returning the
            // identifer;  prefer a source string from locales earlier in the
            // fallback chain, if available
            var source = prevSource || { source: e.source, loc: locale.id };
            return getFromLocale.call(this, cur + 1, id, data, source);
          }
          return getFromLocale.call(this, cur + 1, id, data, prevSource);
        } else {
          throw error(e);
        }
      }
      value.locale = locale.id;
      return value;
    }

    function add(text, locale) {
      var res = new Resource(null, _parser);
      res.source = text;
      locale.resources.push(res);
    }

    function addResource(text) {
      if (_isFrozen) {
        throw new ContextError('Context is frozen');
      }
      _reslinks.push(['text', text]);
    }

    function addDictionary(scriptNode, loc) {
      if (_isFrozen) {
        throw new ContextError('Context is frozen');
      }
      _reslinks.push(['dict', scriptNode, loc]);
    }

    function addJSON(scriptNode, locale) {
      var res = new Resource(null);
      res.ast = JSON.parse(scriptNode.innerHTML);
      locale.resources.push(res);
    }

    function linkResource(uri) {
      if (_isFrozen) {
        throw new ContextError('Context is frozen');
      }
      _reslinks.push([typeof uri === 'function' ? 'template' : 'uri', uri]);
    }

    function link(uri, locale) {
      if (!locale.hasResource(uri)) {
        var res = new Resource(uri, _parser);
        locale.resources.push(res);
      }
    }

    function registerLocales(defaultLocale, availableLocales) {
      if (_isFrozen) {
        throw new ContextError('Context is frozen');
      }

      if (defaultLocale === undefined) {
        return;
      }

      _default = defaultLocale;
      _registered = [];

      if (!availableLocales) {
        availableLocales = [];
      }
      availableLocales.push(defaultLocale);

      // uniquify `available` into `_registered`
      availableLocales.forEach(function l10n_ctx_addlocale(locale) {
        if (typeof locale !== 'string') {
          throw new ContextError('Language codes must be strings');
        }
        if (_registered.indexOf(locale) === -1) {
          _registered.push(locale);
        }
      });
    }

    function negotiate(available, requested, defaultLocale) {
      if (available.indexOf(requested[0]) === -1 ||
          requested[0] === defaultLocale) {
        return [defaultLocale];
      } else {
        return [requested[0], defaultLocale];
      }
    }

    function getLocale(code) {
      if (_locales[code]) {
        return _locales[code];
      }

      var locale = new Locale(code, _parser, _compiler, _emitter);
      _locales[code] = locale;
      // populate the locale with resources
      for (var j = 0; j < _reslinks.length; j++) {
        var res = _reslinks[j];
        if (res[0] === 'text') {
          // a resource added via addResource(String)
          add(res[1], locale);
        } else if (res[0] === 'dict') {
          // a JSON resource added via addDictionary(HTMLScriptElement)
          // only add if no locale was specified or if the locale specified
          // matches the locale being created here
          if (res[2] === undefined || res[2] === locale.id) {
            addJSON(res[1], locale);
          }
        } else if (res[0] === 'uri') {
          // a resource added via linkResource(String)
          link(res[1], locale);
        } else {
          // a resource added via linkResource(Function);  the function
          // passed is a URL template and it takes the current locale's code
          // as an argument
          link(res[1](locale.id), locale);
        }
      }
      locale.ast.body['plural'] = {
        type: 'Macro',
        args: [{
          type: 'Identifier',
          name: 'n'
        }],
        expression: getPluralRule(code)
      };
      return locale;
    }

    function requestLocales() {
      if (_isFrozen && !_isReady) {
        throw new ContextError('Context not ready');
      }

      if (_reslinks.length == 0) {
        warn(new ContextError('Context has no resources; not freezing'));
        return;
      }

      _isFrozen = true;
      _requested = Array.prototype.slice.call(arguments);

      if (_requested.length) {
        _requested.forEach(function l10n_throwError(locale) {
          if (typeof locale !== 'string') {
            throw new ContextError('Language codes must be strings');
          }
        });
      }

      var fallbackChain = negotiate(_registered, _requested, _default);
      // if the negotiator returned something, freeze synchronously
      if (fallbackChain) {
        freeze(fallbackChain);
      }
    }

    function freeze(fallbackChain) {
      _fallbackChain = fallbackChain;
      var locale = getLocale(_fallbackChain[0]);
      if (locale.isReady) {
        setReady();
      } else {
        locale.build(setReady);
      }
    }

    function setReady() {
      _isReady = true;
      _emitter.emit('ready');
    }

    function addEventListener(type, listener) {
      _emitter.addEventListener(type, listener);
    }

    function removeEventListener(type, listener) {
      _emitter.removeEventListener(type, listener);
    }

    function warn(e) {
      _emitter.emit('warning', e);
      return e;
    }

    function error(e) {
      _emitter.emit('error', e);
      return e;
    }
  }

  Context.Error = ContextError;
  Context.RuntimeError = RuntimeError;
  Context.TranslationError = TranslationError;

  function ContextError(message) {
    this.name = 'ContextError';
    this.message = message;
  }
  ContextError.prototype = Object.create(Error.prototype);
  ContextError.prototype.constructor = ContextError;

  function RuntimeError(message, id, supported) {
    ContextError.call(this, message);
    this.name = 'RuntimeError';
    this.entity = id;
    this.supportedLocales = supported.slice();
    this.message = id + ': ' + message + '; tried ' + supported.join(', ');
  }
  RuntimeError.prototype = Object.create(ContextError.prototype);
  RuntimeError.prototype.constructor = RuntimeError;

  function TranslationError(message, id, supported, locale) {
    RuntimeError.call(this, message, id, supported);
    this.name = 'TranslationError';
    this.locale = locale.id;
    this.message = '[' + this.locale + '] ' + id + ': ' + message;
  }
  TranslationError.prototype = Object.create(RuntimeError.prototype);
  TranslationError.prototype.constructor = TranslationError;

  exports.Context = Context;
  exports.Locale = Locale;
  exports.Resource = Resource;

});
<|endoftext|>


<commit_before>(function () {

less.env = location.hostname == '127.0.0.1' ||
           location.hostname == '0.0.0.0'   ||
           location.hostname == 'localhost' ||
           location.protocol == 'file:'     ||
           location.port.length > 0         ? 'development'
                                            : 'production';

// Load styles asynchronously (default: false)
//
// This is set to `false` by default, so that the body
// doesn't start loading before the stylesheets are parsed.
// Setting this to `true` can result in flickering.
//
less.async = false;

//
// Watch mode
//
less.watch   = function () { return this.watchMode = true };
less.unwatch = function () { return this.watchMode = false };

if (less.env === 'development') {
    if (/!watch/.test(location.hash)) {
        less.watch();
    }
    less.watchTimer = setInterval(function () {
        if (less.watchMode) {
            loadStyleSheets(function (root, sheet, lastModified) {
                if (root) {
                    createCSS(root.toCSS(), sheet, lastModified);
                }
            });
        }
    }, 1000);
}

//
// Select all links with the 'rel' attribute set to "less"
//
var sheets = select('link[rel="stylesheet/less"]');

loadStyleSheets(function (root, sheet, env) {
    if (env.local) {
        log("less: loading " + sheet.href + " from local storage.");
    } else {
        createCSS(root.toCSS(), sheet, env.lastModified);
        log("less: parsed " + sheet.href + " successfully.");
    }
});

function select(str) {
    if (!document.querySelectorAll && typeof(jQuery) === "undefined") {
        log("No selector method found");
    } else {
        return (document.querySelectorAll || jQuery).call(document, str);
    }
}

function loadStyleSheets(callback, async) {
    for (var i = 0; i < sheets.length; i++) {
        loadStyleSheet(sheets[i], callback, async);
    }
}

function loadStyleSheet(sheet, callback, async) {
    var css = typeof(localStorage) !== "undefined" && localStorage.getItem(sheet.href);
    var timestamp = typeof(localStorage) !== "undefined" && localStorage.getItem(sheet.href + ':timestamp');
    var styles = { css: css, timestamp: timestamp };

    xhr(sheet.href, async, function (data, lastModified) {
        if (styles && (new(Date)(lastModified).valueOf() ===
                       new(Date)(styles.timestamp).valueOf())) {
            // Use local copy
            createCSS(styles.css, sheet);
            callback(null, sheet, { local: true });
        } else {
            // Use remote copy (re-parse)
            new(less.Parser)({ optimization: 3 }).parse(data, function (e, root) {
                if (e) { return error(e, sheet.href) }
                try {
                    callback(root, sheet, { local: false, lastModified: lastModified });
                } catch (e) {
                    error(e, sheet.href);
                }
            });
        }
    }, function (status) {
        throw new(Error)("Couldn't load " + sheet.href + " (" + status + ")");
    });
}

function createCSS(styles, sheet, lastModified) {
    var css, title, id;

    // If there is no title set, use the filename, minus the extension
    title = sheet.title || sheet.href.match(/(?:^|\/)([-\w]+)\.[a-z]+$/i)[1];
    id = '-less-' + title;

    // If the stylesheet doesn't exist, create a new node
    if ((css = document.getElementById(id)) === null) {
        css = document.createElement('style');
        css.type = 'text/css';
        css.media = 'screen';
        css.title = title;
        css.id = id;
        document.getElementsByTagName('head')[0].appendChild(css);
    }

    if (css.styleSheet) { // IE
        try {
            css.styleSheet.cssText = styles;
        } catch (e) {
            throw new(Error)("Couldn't reassign styleSheet.cssText.");
        }
    } else {
        if (css.childNodes.length > 0) {
            css.removeChild(css.childNodes[0]);
        }
        css.appendChild(document.createTextNode(styles));
    }

    // Don't update the local store if the file wasn't modified
    if (lastModified && typeof(localStorage) !== "undefined") {
        localStorage.setItem(sheet.href, styles);
        localStorage.setItem(sheet.href + ':timestamp', lastModified);
    }
}

function xhr(url, async, callback, errback) {
    var xhr = getXMLHttpRequest();

    if (window.location.protocol === "file:") {
        xhr.open('GET', url, false);
        xhr.send(null);
        if (xhr.status === 0) {
            callback(xhr.responseText);
        } else {
            errback(xhr.status);
        }
    } else {
        xhr.open('GET', url, async || less.async);
        xhr.onreadystatechange = function () {
            if (xhr.readyState == 4) {
                if (xhr.status >= 200 && xhr.status < 300) {
                    callback(xhr.responseText,
                             xhr.getResponseHeader("Last-Modified"));
                } else if (typeof(errback) === 'function') {
                    errback(xhr.status);
                }
            }
        };
        xhr.send(null);
    }
}

function getXMLHttpRequest() {
    if (window.XMLHttpRequest) {
        return new(XMLHttpRequest);
    } else {
        try {
            return new(ActiveXObject)("MSXML2.XMLHTTP.3.0");
        } catch (e) {
            log("less: browser doesn't support AJAX.");
            return null;
        }
    }
}

function log(str) {
    if (less.env == 'development' && typeof(console) !== "undefined") { console.log(str) }
}

function error(e, href) {
    if (document.getElementById('less-error-message')) { return }

    var template = ['
',
                        '
[-1]{0}
',
                        '
[0]{current}
',
                        '
[1]{2}
',
                    '
'].join('\n');

    var elem = document.createElement('div'), timer;
    elem.id = "less-error-message";
    elem.innerHTML = '
' + (e.message || 'There is an error in your .less file') + '
' +
                     '
' + href + " "                +
                     'on line '     + e.line + ', column ' + (e.column + 1)         + ':
' +
                     template.replace(/\[(-?\d)\]/g, function (_, i) {
                         return e.line + parseInt(i);
                     }).replace(/\{(\d)\}/g, function (_, i) {
                         return e.extract[parseInt(i)];
                     }).replace(/\{current\}/, e.extract[1].slice(0, e.column)      +
                                               ''               +
                                               e.extract[1].slice(e.column)         +
                                               '');
    // CSS for error messages
    createCSS([
        '#less-error-message span {',
            'margin-right: 15px;',
        '}',
        '#less-error-message pre {',
            'color: #ee4444;',
            'padding: 4px 0;',
            'margin: 0;',
        '}',
        '#less-error-message pre.ctx {',
            'color: #dd7777;',
        '}',
        '#less-error-message h3 {',
            'padding: 15px 0 5px 0;',
            'margin: 0;',
        '}',
        '#less-error-message a {',
            'color: #10a',
        '}',
        '#less-error-message .error {',
            'color: red;',
            'font-weight: bold;',
            'padding-bottom: 2px;',
            'border-bottom: 1px dashed red;',
        '}'
    ].join(''), { title: 'error-message' });

    elem.style.cssText = [
        "font-family: Arial, sans-serif",
        "border: 1px solid #e00",
        "background-color: #eee",
        "border-radius: 5px",
        "color: #e00",
        "padding: 15px",
        "margin-bottom: 15px"
    ].join(';');

    if (less.env == 'development') {
        timer = setInterval(function () {
            if (document.body) {
                document.body.insertBefore(elem, document.body.childNodes[0]);
                clearInterval(timer);
            }
        }, 10);
    }
}

//
// Used by `@import` directives
//
less.Parser.importer = function (path, paths, callback) {
    loadStyleSheet({ href: path, title: path }, function (root) {
        callback(root);
    });
};

})();
<commit_msg>set optimization level depending on less.env<commit_after>(function () {

less.env = location.hostname == '127.0.0.1' ||
           location.hostname == '0.0.0.0'   ||
           location.hostname == 'localhost' ||
           location.protocol == 'file:'     ||
           location.port.length > 0         ? 'development'
                                            : 'production';

// Load styles asynchronously (default: false)
//
// This is set to `false` by default, so that the body
// doesn't start loading before the stylesheets are parsed.
// Setting this to `true` can result in flickering.
//
less.async = false;

//
// Watch mode
//
less.watch   = function () { return this.watchMode = true };
less.unwatch = function () { return this.watchMode = false };

if (less.env === 'development') {
    less.optimization = 0;

    if (/!watch/.test(location.hash)) {
        less.watch();
    }
    less.watchTimer = setInterval(function () {
        if (less.watchMode) {
            loadStyleSheets(function (root, sheet, lastModified) {
                if (root) {
                    createCSS(root.toCSS(), sheet, lastModified);
                }
            });
        }
    }, 1000);
} else {
    less.optimization = 3;
}

//
// Select all links with the 'rel' attribute set to "less"
//
var sheets = select('link[rel="stylesheet/less"]');

loadStyleSheets(function (root, sheet, env) {
    if (env.local) {
        log("less: loading " + sheet.href + " from local storage.");
    } else {
        createCSS(root.toCSS(), sheet, env.lastModified);
        log("less: parsed " + sheet.href + " successfully.");
    }
});

function select(str) {
    if (!document.querySelectorAll && typeof(jQuery) === "undefined") {
        log("No selector method found");
    } else {
        return (document.querySelectorAll || jQuery).call(document, str);
    }
}

function loadStyleSheets(callback, async) {
    for (var i = 0; i < sheets.length; i++) {
        loadStyleSheet(sheets[i], callback, async);
    }
}

function loadStyleSheet(sheet, callback, async) {
    var css = typeof(localStorage) !== "undefined" && localStorage.getItem(sheet.href);
    var timestamp = typeof(localStorage) !== "undefined" && localStorage.getItem(sheet.href + ':timestamp');
    var styles = { css: css, timestamp: timestamp };

    xhr(sheet.href, async, function (data, lastModified) {
        if (styles && (new(Date)(lastModified).valueOf() ===
                       new(Date)(styles.timestamp).valueOf())) {
            // Use local copy
            createCSS(styles.css, sheet);
            callback(null, sheet, { local: true });
        } else {
            // Use remote copy (re-parse)
            new(less.Parser)({
                optimization: less.optimization
            }).parse(data, function (e, root) {
                if (e) { return error(e, sheet.href) }
                try {
                    callback(root, sheet, { local: false, lastModified: lastModified });
                } catch (e) {
                    error(e, sheet.href);
                }
            });
        }
    }, function (status) {
        throw new(Error)("Couldn't load " + sheet.href + " (" + status + ")");
    });
}

function createCSS(styles, sheet, lastModified) {
    var css, title, id;

    // If there is no title set, use the filename, minus the extension
    title = sheet.title || sheet.href.match(/(?:^|\/)([-\w]+)\.[a-z]+$/i)[1];
    id = '-less-' + title;

    // If the stylesheet doesn't exist, create a new node
    if ((css = document.getElementById(id)) === null) {
        css = document.createElement('style');
        css.type = 'text/css';
        css.media = 'screen';
        css.title = title;
        css.id = id;
        document.getElementsByTagName('head')[0].appendChild(css);
    }

    if (css.styleSheet) { // IE
        try {
            css.styleSheet.cssText = styles;
        } catch (e) {
            throw new(Error)("Couldn't reassign styleSheet.cssText.");
        }
    } else {
        if (css.childNodes.length > 0) {
            css.removeChild(css.childNodes[0]);
        }
        css.appendChild(document.createTextNode(styles));
    }

    // Don't update the local store if the file wasn't modified
    if (lastModified && typeof(localStorage) !== "undefined") {
        localStorage.setItem(sheet.href, styles);
        localStorage.setItem(sheet.href + ':timestamp', lastModified);
    }
}

function xhr(url, async, callback, errback) {
    var xhr = getXMLHttpRequest();

    if (window.location.protocol === "file:") {
        xhr.open('GET', url, false);
        xhr.send(null);
        if (xhr.status === 0) {
            callback(xhr.responseText);
        } else {
            errback(xhr.status);
        }
    } else {
        xhr.open('GET', url, async || less.async);
        xhr.onreadystatechange = function () {
            if (xhr.readyState == 4) {
                if (xhr.status >= 200 && xhr.status < 300) {
                    callback(xhr.responseText,
                             xhr.getResponseHeader("Last-Modified"));
                } else if (typeof(errback) === 'function') {
                    errback(xhr.status);
                }
            }
        };
        xhr.send(null);
    }
}

function getXMLHttpRequest() {
    if (window.XMLHttpRequest) {
        return new(XMLHttpRequest);
    } else {
        try {
            return new(ActiveXObject)("MSXML2.XMLHTTP.3.0");
        } catch (e) {
            log("less: browser doesn't support AJAX.");
            return null;
        }
    }
}

function log(str) {
    if (less.env == 'development' && typeof(console) !== "undefined") { console.log(str) }
}

function error(e, href) {
    if (document.getElementById('less-error-message')) { return }

    var template = ['
',
                        '
[-1]{0}
',
                        '
[0]{current}
',
                        '
[1]{2}
',
                    '
'].join('\n');

    var elem = document.createElement('div'), timer;
    elem.id = "less-error-message";
    elem.innerHTML = '
' + (e.message || 'There is an error in your .less file') + '
' +
                     '
' + href + " "                +
                     'on line '     + e.line + ', column ' + (e.column + 1)         + ':
' +
                     template.replace(/\[(-?\d)\]/g, function (_, i) {
                         return e.line + parseInt(i);
                     }).replace(/\{(\d)\}/g, function (_, i) {
                         return e.extract[parseInt(i)];
                     }).replace(/\{current\}/, e.extract[1].slice(0, e.column)      +
                                               ''               +
                                               e.extract[1].slice(e.column)         +
                                               '');
    // CSS for error messages
    createCSS([
        '#less-error-message span {',
            'margin-right: 15px;',
        '}',
        '#less-error-message pre {',
            'color: #ee4444;',
            'padding: 4px 0;',
            'margin: 0;',
        '}',
        '#less-error-message pre.ctx {',
            'color: #dd7777;',
        '}',
        '#less-error-message h3 {',
            'padding: 15px 0 5px 0;',
            'margin: 0;',
        '}',
        '#less-error-message a {',
            'color: #10a',
        '}',
        '#less-error-message .error {',
            'color: red;',
            'font-weight: bold;',
            'padding-bottom: 2px;',
            'border-bottom: 1px dashed red;',
        '}'
    ].join(''), { title: 'error-message' });

    elem.style.cssText = [
        "font-family: Arial, sans-serif",
        "border: 1px solid #e00",
        "background-color: #eee",
        "border-radius: 5px",
        "color: #e00",
        "padding: 15px",
        "margin-bottom: 15px"
    ].join(';');

    if (less.env == 'development') {
        timer = setInterval(function () {
            if (document.body) {
                document.body.insertBefore(elem, document.body.childNodes[0]);
                clearInterval(timer);
            }
        }, 10);
    }
}

//
// Used by `@import` directives
//
less.Parser.importer = function (path, paths, callback) {
    loadStyleSheet({ href: path, title: path }, function (root) {
        callback(root);
    });
};

})();
<|endoftext|>