/* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Skywriter. * * The Initial Developer of the Original Code is * Mozilla. * Portions created by the Initial Developer are Copyright (C) 2009 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Joe Walker (jwalker@mozilla.com) * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ define(function(require, exports, module) { /** * Some types can detect validity, that is to say they can distinguish between * valid and invalid values. * TODO: Change these constants to be numbers for more performance? */ var Status = { /** * The conversion process worked without any problem, and the value is * valid. There are a number of failure states, so the best way to check * for failure is (x !== Status.VALID) */ VALID: { toString: function() { return 'VALID'; }, valueOf: function() { return 0; } }, /** * A conversion process failed, however it was noted that the string * provided to 'parse()' could be VALID by the addition of more characters, * so the typing may not be actually incorrect yet, just unfinished. * @see Status.INVALID */ INCOMPLETE: { toString: function() { return 'INCOMPLETE'; }, valueOf: function() { return 1; } }, /** * The conversion process did not work, the value should be null and a * reason for failure should have been provided. In addition some completion * values may be available. * @see Status.INCOMPLETE */ INVALID: { toString: function() { return 'INVALID'; }, valueOf: function() { return 2; } }, /** * A combined status is the worser of the provided statuses */ combine: function(statuses) { var combined = Status.VALID; for (var i = 0; i < statuses.length; i++) { if (statuses[i].valueOf() > combined.valueOf()) { combined = statuses[i]; } } return combined; } }; exports.Status = Status; /** * The type.parse() method returns a Conversion to inform the user about not * only the result of a Conversion but also about what went wrong. * We could use an exception, and throw if the conversion failed, but that * seems to violate the idea that exceptions should be exceptional. Typos are * not. Also in order to store both a status and a message we'd still need * some sort of exception type... */ function Conversion(value, status, message, predictions) { /** * The result of the conversion process. Will be null if status != VALID */ this.value = value; /** * The status of the conversion. * @see Status */ this.status = status || Status.VALID; /** * A message to go with the conversion. This could be present for any status * including VALID in the case where we want to note a warning for example. * I18N: On the one hand this nasty and un-internationalized, however with * a command line it is hard to know where to start. */ this.message = message; /** * A array of strings which are the systems best guess at better inputs than * the one presented. * We generally expect there to be about 7 predictions (to match human list * comprehension ability) however it is valid to provide up to about 20, * or less. It is the job of the predictor to decide a smart cut-off. * For example if there are 4 very good matches and 4 very poor ones, * probably only the 4 very good matches should be presented. */ this.predictions = predictions || []; } exports.Conversion = Conversion; /** * Most of our types are 'static' e.g. there is only one type of 'text', however * some types like 'selection' and 'deferred' are customizable. The basic * Type type isn't useful, but does provide documentation about what types do. */ function Type() { }; Type.prototype = { /** * Convert the given value to a string representation. * Where possible, there should be round-tripping between values and their * string representations. */ stringify: function(value) { throw new Error("not implemented"); }, /** * Convert the given str to an instance of this type. * Where possible, there should be round-tripping between values and their * string representations. * @return Conversion */ parse: function(str) { throw new Error("not implemented"); }, /** * The plug-in system, and other things need to know what this type is * called. The name alone is not enough to fully specify a type. Types like * 'selection' and 'deferred' need extra data, however this function returns * only the name, not the extra data. *

In old bespin, equality was based on the name. This may turn out to be * important in Ace too. */ name: undefined, /** * If there is some concept of a higher value, return it, * otherwise return undefined. */ increment: function(value) { return undefined; }, /** * If there is some concept of a lower value, return it, * otherwise return undefined. */ decrement: function(value) { return undefined; }, /** * There is interesting information (like predictions) in a conversion of * nothing, the output of this can sometimes be customized. * @return Conversion */ getDefault: function() { return this.parse(''); } }; exports.Type = Type; /** * Private registry of types * Invariant: types[name] = type.name */ var types = {}; /** * Add a new type to the list available to the system. * You can pass 2 things to this function - either an instance of Type, in * which case we return this instance when #getType() is called with a 'name' * that matches type.name. * Also you can pass in a constructor (i.e. function) in which case when * #getType() is called with a 'name' that matches Type.prototype.name we will * pass the typeSpec into this constructor. See #reconstituteType(). */ exports.registerType = function(type) { if (typeof type === 'object') { if (type instanceof Type) { if (!type.name) { throw new Error('All registered types must have a name'); } types[type.name] = type; } else { throw new Error('Can\'t registerType using: ' + type); } } else if (typeof type === 'function') { if (!type.prototype.name) { throw new Error('All registered types must have a name'); } types[type.prototype.name] = type; } else { throw new Error('Unknown type: ' + type); } }; exports.registerTypes = function registerTypes(types) { Object.keys(types).forEach(function (name) { var type = types[name]; type.name = name; exports.registerType(type); }); }; /** * Remove a type from the list available to the system */ exports.deregisterType = function(type) { delete types[type.name]; }; /** * See description of #exports.registerType() */ function reconstituteType(name, typeSpec) { if (name.substr(-2) === '[]') { // i.e. endsWith('[]') var subtypeName = name.slice(0, -2); return new types['array'](subtypeName); } var type = types[name]; if (typeof type === 'function') { type = new type(typeSpec); } return type; } /** * Find a type, previously registered using #registerType() */ exports.getType = function(typeSpec) { if (typeof typeSpec === 'string') { return reconstituteType(typeSpec); } if (typeof typeSpec === 'object') { if (!typeSpec.name) { throw new Error('Missing \'name\' member to typeSpec'); } return reconstituteType(typeSpec.name, typeSpec); } throw new Error('Can\'t extract type from ' + typeSpec); }; });