rename Type methods: s/toString/stringify and s/fromString/parse to avoid clash with built in meaning for toString. Also add lots of documentation, a new DeferredType, and lots more checks to how types are created
This commit is contained in:
parent
84a174f9d7
commit
e544ad30fc
3 changed files with 137 additions and 54 deletions
|
|
@ -240,7 +240,7 @@ Settings.prototype = {
|
|||
if (data.hasOwnProperty(key)) {
|
||||
var setting = this._settings[key];
|
||||
if (setting) {
|
||||
var value = setting.type.fromString(data[key]);
|
||||
var value = setting.type.parse(data[key]);
|
||||
this.set(key, value);
|
||||
} else {
|
||||
this.set(key, data[key]);
|
||||
|
|
@ -254,7 +254,7 @@ Settings.prototype = {
|
|||
*/
|
||||
_saveToObject: function() {
|
||||
return this.getSettingNames().map(function(key) {
|
||||
return this._settings[key].type.toString(this.get(key));
|
||||
return this._settings[key].type.stringify(this.get(key));
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -49,28 +49,41 @@ var Status = {
|
|||
*/
|
||||
VALID: 1,
|
||||
|
||||
/**
|
||||
* The conversion process did not work like Status.INVALID, 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: 2,
|
||||
|
||||
/**
|
||||
* 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: 2,
|
||||
INVALID: 3,
|
||||
|
||||
/**
|
||||
* The conversion process did not work like Status.INVALID, however it was
|
||||
* noted that the string provided to 'fromString()' could be VALID by
|
||||
* the addition of more characters, so the typing may not be actually
|
||||
* incorrect yet, just unfinished.
|
||||
* @see Status.INVALID
|
||||
* A combined status is the worser of the provided statuses
|
||||
*/
|
||||
INCOMPLETE: 3
|
||||
combine: function(statuses) {
|
||||
var combined = Status.VALID;
|
||||
for (var i = 0; i < arguments; i++) {
|
||||
if (arguments[i] > combined) {
|
||||
combined = arguments[i];
|
||||
}
|
||||
}
|
||||
return combined;
|
||||
}
|
||||
};
|
||||
exports.Status = Status;
|
||||
|
||||
/**
|
||||
* The type.fromString() method returns a Conversion to inform the user about
|
||||
* not only the result of a Conversion but also about what went wrong.
|
||||
* 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
|
||||
|
|
@ -123,7 +136,7 @@ Type.prototype = {
|
|||
* Where possible, there should be round-tripping between values and their
|
||||
* string representations.
|
||||
*/
|
||||
toString: function(value) { throw new Error("not implemented"); },
|
||||
stringify: function(value) { throw new Error("not implemented"); },
|
||||
|
||||
/**
|
||||
* Convert the given <tt>str</tt> to an instance of this type.
|
||||
|
|
@ -131,7 +144,7 @@ Type.prototype = {
|
|||
* string representations.
|
||||
* @return Conversion
|
||||
*/
|
||||
fromString: function(str) { throw new Error("not implemented"); },
|
||||
parse: function(str) { throw new Error("not implemented"); },
|
||||
|
||||
/**
|
||||
* The plug-in system, and other things need to know what this type is
|
||||
|
|
@ -152,10 +165,35 @@ exports.Type = Type;
|
|||
var types = {};
|
||||
|
||||
/**
|
||||
* Add a new type to the list available to the system
|
||||
* 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) {
|
||||
types[type.name] = 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);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -165,30 +203,34 @@ exports.deregisterType = function(type) {
|
|||
delete types[type.name];
|
||||
};
|
||||
|
||||
/**
|
||||
* See description of #exports.registerType()
|
||||
*/
|
||||
function reconstituteType(name, typeSpec) {
|
||||
var type = types[name];
|
||||
if (typeof type === 'function') {
|
||||
// TODO: should we complain if typeSpec is a string?
|
||||
type = new type(typeSpec);
|
||||
}
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a type, previously registered using #registerType()
|
||||
*/
|
||||
exports.getType = function(typeSpec) {
|
||||
var type;
|
||||
if (typeof typeSpec === 'string') {
|
||||
type = types[typeSpec];
|
||||
return reconstituteType(typeSpec, typeSpec);
|
||||
}
|
||||
|
||||
if (typeof typeSpec == 'object') {
|
||||
if (!typeSpec.name) {
|
||||
throw new Error('Missing \'name\' member to typeSpec');
|
||||
}
|
||||
|
||||
type = types[typeSpec.name];
|
||||
return reconstituteType(typeSpec.name, typeSpec);
|
||||
}
|
||||
|
||||
if (type instanceof Type) {
|
||||
return type;
|
||||
}
|
||||
|
||||
if (typeof type === 'function') {
|
||||
return type(typeSpec);
|
||||
}
|
||||
throw new Error('Can\'t extract type from ' + typeSpec);
|
||||
};
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ define(function(require, exports, module) {
|
|||
var types = require("pilot/types");
|
||||
var Type = types.Type;
|
||||
var Conversion = types.Conversion;
|
||||
var Status = types.Status;
|
||||
|
||||
/**
|
||||
* These are the basic types that we accept. They are vaguely based on the
|
||||
|
|
@ -59,13 +60,13 @@ var Conversion = types.Conversion;
|
|||
*/
|
||||
var text = new Type();
|
||||
|
||||
text.toString = function(value) {
|
||||
text.stringify = function(value) {
|
||||
return value;
|
||||
};
|
||||
|
||||
text.fromString = function(value) {
|
||||
text.parse = function(value) {
|
||||
if (typeof value != 'string') {
|
||||
throw new Error('non-string passed to text.fromString()');
|
||||
throw new Error('non-string passed to text.parse()');
|
||||
}
|
||||
return new Conversion(value);
|
||||
};
|
||||
|
|
@ -77,16 +78,16 @@ text.name = 'text';
|
|||
*/
|
||||
var number = new Type();
|
||||
|
||||
number.toString = function(value) {
|
||||
number.stringify = function(value) {
|
||||
if (!value) {
|
||||
return null;
|
||||
}
|
||||
return '' + value;
|
||||
};
|
||||
|
||||
number.fromString = function(value) {
|
||||
number.parse = function(value) {
|
||||
if (typeof value != 'string') {
|
||||
throw new Error('non-string passed to number.fromString()');
|
||||
throw new Error('non-string passed to number.parse()');
|
||||
}
|
||||
|
||||
var reply = new Conversion(parseInt(value, 10));
|
||||
|
|
@ -103,24 +104,29 @@ number.name = 'number';
|
|||
/**
|
||||
* One of a known set of options
|
||||
*/
|
||||
function SelectionType(data) {
|
||||
this._data = data;
|
||||
function SelectionType(typeSpec) {
|
||||
if (!Array.isArray(typeSpec.data) && typeof typeSpec.data !== 'function') {
|
||||
throw new Error('instances of SelectionType need typeSpec.data to be an array or function that returns an array:' + JSON.stringify(typeSpec));
|
||||
}
|
||||
Object.keys(typeSpec).forEach(function(key) {
|
||||
this[key] = typeSpec[key];
|
||||
}, this);
|
||||
};
|
||||
|
||||
SelectionType.prototype = new Type();
|
||||
|
||||
SelectionType.prototype.toString = function(value) {
|
||||
SelectionType.prototype.stringify = function(value) {
|
||||
return value;
|
||||
};
|
||||
|
||||
SelectionType.prototype.fromString = function(value) {
|
||||
SelectionType.prototype.parse = function(value) {
|
||||
if (typeof value != 'string') {
|
||||
throw new Error('non-string passed to fromString()');
|
||||
throw new Error('non-string passed to parse()');
|
||||
}
|
||||
if (!this._data) {
|
||||
if (!this.data) {
|
||||
throw new Error('Missing data on selection type extension.');
|
||||
}
|
||||
var data = (typeof(this._data) === "function") ? this._data() : this._data;
|
||||
var data = (typeof(this.data) === "function") ? this.data() : this.data;
|
||||
|
||||
var match = false;
|
||||
var completions = [];
|
||||
|
|
@ -143,37 +149,70 @@ SelectionType.prototype.fromString = function(value) {
|
|||
// TODO: better completions - we're just using the extensions
|
||||
return new Conversion(null,
|
||||
status,
|
||||
'Can\'t convert "' + value + '" to a selection.',
|
||||
'Can\'t convert \'' + value + '\' to a selection.',
|
||||
completions);
|
||||
}
|
||||
};
|
||||
|
||||
SelectionType.prototype.name = 'selection';
|
||||
|
||||
/**
|
||||
* SelectionType is a base class for other types
|
||||
*/
|
||||
exports.SelectionType = SelectionType;
|
||||
|
||||
/**
|
||||
* true/false values
|
||||
*/
|
||||
var bool = new SelectionType([ 'true', 'false' ]);
|
||||
var bool = new SelectionType({
|
||||
name: 'bool',
|
||||
data: [ 'true', 'false' ],
|
||||
stringify: function(value) {
|
||||
return '' + value;
|
||||
},
|
||||
parse: function(value) {
|
||||
var conversion = SelectionType.prototype.parse(value);
|
||||
|
||||
bool.toString = function(value) {
|
||||
return '' + value;
|
||||
if (conversion.value === 'true') {
|
||||
conversion.value = true;
|
||||
}
|
||||
if (conversion.value === 'false') {
|
||||
conversion.value = false;
|
||||
}
|
||||
|
||||
return conversion;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* One of a known set of options
|
||||
*/
|
||||
function DeferredType(typeSpec) {
|
||||
if (typeof typeSpec.defer !== 'function') {
|
||||
throw new Error('Instances of DeferredType need typeSpec.defer to be a function that returns a type');
|
||||
}
|
||||
Object.keys(typeSpec).forEach(function(key) {
|
||||
this[key] = typeSpec[key];
|
||||
}, this);
|
||||
};
|
||||
|
||||
bool.fromString = function(value) {
|
||||
var conversion = SelectionType.prototype.fromString(value);
|
||||
DeferredType.prototype = new Type();
|
||||
|
||||
if (conversion.value === 'true') {
|
||||
conversion.value = true;
|
||||
}
|
||||
if (conversion.value === 'false') {
|
||||
conversion.value = false;
|
||||
}
|
||||
|
||||
return conversion;
|
||||
DeferredType.prototype.stringify = function(value) {
|
||||
return this.defer.stringify(value);
|
||||
};
|
||||
|
||||
bool.name = 'bool';
|
||||
DeferredType.prototype.parse = function(value) {
|
||||
return this.defer.parse(value);
|
||||
};
|
||||
|
||||
DeferredType.prototype.name = 'deferred';
|
||||
|
||||
/**
|
||||
* DeferredType is a base class for other types
|
||||
*/
|
||||
exports.DeferredType = DeferredType;
|
||||
|
||||
/**
|
||||
* Registration and de-registration.
|
||||
|
|
@ -183,6 +222,7 @@ exports.startup = function() {
|
|||
types.registerType(number);
|
||||
types.registerType(bool);
|
||||
types.registerType(SelectionType);
|
||||
types.registerType(DeferredType);
|
||||
};
|
||||
|
||||
exports.shutdown = function() {
|
||||
|
|
@ -190,6 +230,7 @@ exports.shutdown = function() {
|
|||
types.unregisterType(number);
|
||||
types.unregisterType(bool);
|
||||
types.unregisterType(SelectionType);
|
||||
types.unregisterType(DeferredType);
|
||||
};
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue