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)) {
|
if (data.hasOwnProperty(key)) {
|
||||||
var setting = this._settings[key];
|
var setting = this._settings[key];
|
||||||
if (setting) {
|
if (setting) {
|
||||||
var value = setting.type.fromString(data[key]);
|
var value = setting.type.parse(data[key]);
|
||||||
this.set(key, value);
|
this.set(key, value);
|
||||||
} else {
|
} else {
|
||||||
this.set(key, data[key]);
|
this.set(key, data[key]);
|
||||||
|
|
@ -254,7 +254,7 @@ Settings.prototype = {
|
||||||
*/
|
*/
|
||||||
_saveToObject: function() {
|
_saveToObject: function() {
|
||||||
return this.getSettingNames().map(function(key) {
|
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));
|
}.bind(this));
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -49,28 +49,41 @@ var Status = {
|
||||||
*/
|
*/
|
||||||
VALID: 1,
|
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
|
* The conversion process did not work, the value should be null and a
|
||||||
* reason for failure should have been provided. In addition some completion
|
* reason for failure should have been provided. In addition some completion
|
||||||
* values may be available.
|
* values may be available.
|
||||||
* @see Status.INCOMPLETE
|
* @see Status.INCOMPLETE
|
||||||
*/
|
*/
|
||||||
INVALID: 2,
|
INVALID: 3,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The conversion process did not work like Status.INVALID, however it was
|
* A combined status is the worser of the provided statuses
|
||||||
* 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
|
|
||||||
*/
|
*/
|
||||||
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;
|
exports.Status = Status;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The type.fromString() method returns a Conversion to inform the user about
|
* The type.parse() method returns a Conversion to inform the user about not
|
||||||
* not only the result of a Conversion but also about what went wrong.
|
* 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
|
* 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
|
* 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
|
* 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
|
* Where possible, there should be round-tripping between values and their
|
||||||
* string representations.
|
* 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.
|
* Convert the given <tt>str</tt> to an instance of this type.
|
||||||
|
|
@ -131,7 +144,7 @@ Type.prototype = {
|
||||||
* string representations.
|
* string representations.
|
||||||
* @return Conversion
|
* @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
|
* The plug-in system, and other things need to know what this type is
|
||||||
|
|
@ -152,10 +165,35 @@ exports.Type = Type;
|
||||||
var types = {};
|
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) {
|
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];
|
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()
|
* Find a type, previously registered using #registerType()
|
||||||
*/
|
*/
|
||||||
exports.getType = function(typeSpec) {
|
exports.getType = function(typeSpec) {
|
||||||
var type;
|
|
||||||
if (typeof typeSpec === 'string') {
|
if (typeof typeSpec === 'string') {
|
||||||
type = types[typeSpec];
|
return reconstituteType(typeSpec, typeSpec);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof typeSpec == 'object') {
|
if (typeof typeSpec == 'object') {
|
||||||
if (!typeSpec.name) {
|
if (!typeSpec.name) {
|
||||||
throw new Error('Missing \'name\' member to typeSpec');
|
throw new Error('Missing \'name\' member to typeSpec');
|
||||||
}
|
}
|
||||||
|
return reconstituteType(typeSpec.name, typeSpec);
|
||||||
type = types[typeSpec.name];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (type instanceof Type) {
|
throw new Error('Can\'t extract type from ' + typeSpec);
|
||||||
return type;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof type === 'function') {
|
|
||||||
return type(typeSpec);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,7 @@ define(function(require, exports, module) {
|
||||||
var types = require("pilot/types");
|
var types = require("pilot/types");
|
||||||
var Type = types.Type;
|
var Type = types.Type;
|
||||||
var Conversion = types.Conversion;
|
var Conversion = types.Conversion;
|
||||||
|
var Status = types.Status;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* These are the basic types that we accept. They are vaguely based on the
|
* 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();
|
var text = new Type();
|
||||||
|
|
||||||
text.toString = function(value) {
|
text.stringify = function(value) {
|
||||||
return value;
|
return value;
|
||||||
};
|
};
|
||||||
|
|
||||||
text.fromString = function(value) {
|
text.parse = function(value) {
|
||||||
if (typeof value != 'string') {
|
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);
|
return new Conversion(value);
|
||||||
};
|
};
|
||||||
|
|
@ -77,16 +78,16 @@ text.name = 'text';
|
||||||
*/
|
*/
|
||||||
var number = new Type();
|
var number = new Type();
|
||||||
|
|
||||||
number.toString = function(value) {
|
number.stringify = function(value) {
|
||||||
if (!value) {
|
if (!value) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return '' + value;
|
return '' + value;
|
||||||
};
|
};
|
||||||
|
|
||||||
number.fromString = function(value) {
|
number.parse = function(value) {
|
||||||
if (typeof value != 'string') {
|
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));
|
var reply = new Conversion(parseInt(value, 10));
|
||||||
|
|
@ -103,24 +104,29 @@ number.name = 'number';
|
||||||
/**
|
/**
|
||||||
* One of a known set of options
|
* One of a known set of options
|
||||||
*/
|
*/
|
||||||
function SelectionType(data) {
|
function SelectionType(typeSpec) {
|
||||||
this._data = data;
|
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 = new Type();
|
||||||
|
|
||||||
SelectionType.prototype.toString = function(value) {
|
SelectionType.prototype.stringify = function(value) {
|
||||||
return value;
|
return value;
|
||||||
};
|
};
|
||||||
|
|
||||||
SelectionType.prototype.fromString = function(value) {
|
SelectionType.prototype.parse = function(value) {
|
||||||
if (typeof value != 'string') {
|
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.');
|
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 match = false;
|
||||||
var completions = [];
|
var completions = [];
|
||||||
|
|
@ -143,37 +149,70 @@ SelectionType.prototype.fromString = function(value) {
|
||||||
// TODO: better completions - we're just using the extensions
|
// TODO: better completions - we're just using the extensions
|
||||||
return new Conversion(null,
|
return new Conversion(null,
|
||||||
status,
|
status,
|
||||||
'Can\'t convert "' + value + '" to a selection.',
|
'Can\'t convert \'' + value + '\' to a selection.',
|
||||||
completions);
|
completions);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
SelectionType.prototype.name = 'selection';
|
SelectionType.prototype.name = 'selection';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SelectionType is a base class for other types
|
||||||
|
*/
|
||||||
|
exports.SelectionType = SelectionType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* true/false values
|
* 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) {
|
if (conversion.value === 'true') {
|
||||||
return '' + value;
|
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) {
|
DeferredType.prototype = new Type();
|
||||||
var conversion = SelectionType.prototype.fromString(value);
|
|
||||||
|
|
||||||
if (conversion.value === 'true') {
|
DeferredType.prototype.stringify = function(value) {
|
||||||
conversion.value = true;
|
return this.defer.stringify(value);
|
||||||
}
|
|
||||||
if (conversion.value === 'false') {
|
|
||||||
conversion.value = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return conversion;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
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.
|
* Registration and de-registration.
|
||||||
|
|
@ -183,6 +222,7 @@ exports.startup = function() {
|
||||||
types.registerType(number);
|
types.registerType(number);
|
||||||
types.registerType(bool);
|
types.registerType(bool);
|
||||||
types.registerType(SelectionType);
|
types.registerType(SelectionType);
|
||||||
|
types.registerType(DeferredType);
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.shutdown = function() {
|
exports.shutdown = function() {
|
||||||
|
|
@ -190,6 +230,7 @@ exports.shutdown = function() {
|
||||||
types.unregisterType(number);
|
types.unregisterType(number);
|
||||||
types.unregisterType(bool);
|
types.unregisterType(bool);
|
||||||
types.unregisterType(SelectionType);
|
types.unregisterType(SelectionType);
|
||||||
|
types.unregisterType(DeferredType);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue