Code comments and other code cleanup

This commit is contained in:
Patrick Steele-Idem 2015-04-24 11:40:58 -06:00
parent 9116bb7009
commit 2c95b1ed20
6 changed files with 355 additions and 61 deletions

View File

@ -30,9 +30,7 @@ TypeConverter.convert = function (value, targetType, allowExpressions) {
return value;
}
if (targetType === 'expression') {
if (targetType === 'expression' || targetType === 'object' || targetType === 'array') {
if (value === '') {
value = 'null';
}

View File

@ -150,6 +150,10 @@ module.exports = makeClass({
nestedTag.isNestedTag = true;
if (!nestedTag.targetProperty) {
nestedTag.targetProperty = nestedTag.name;
}
this.nestedTags[nestedTag.name] = nestedTag;
},
forEachNestedTag: function (callback, thisObj) {

View File

@ -10,18 +10,50 @@ function AttrHandlers(attr){
}
AttrHandlers.prototype = {
/**
* The attribute type. One of the following:
* - string (the default)
* - expression (a JavaScript expression)
* - number
* - integer
* - int
* - boolean
* - float
* - double
* - object
* - array
*
*/
type: function(value) {
var attr = this.attr;
attr.type = value;
},
/**
* The name of the target property to use when mapping
* the attribute to a property on the target object.
*/
targetProperty: function(value) {
var attr = this.attr;
attr.targetProperty = value;
},
/**
* The "default-value" property allows a default value
* to be provided when the attribute is not declared
* on the custom tag.
*/
defaultValue: function(value) {
var attr = this.attr;
attr.defaultValue = value;
},
/**
* The "pattern" property allows the attribute
* to be matched based on a simplified regular expression.
*
* Example:
*
* "pattern": "myprefix-*"
*/
pattern: function(value) {
var attr = this.attr;
if (value === true) {
@ -29,29 +61,80 @@ AttrHandlers.prototype = {
attr.pattern = patternRegExp;
}
},
/**
* If "allow-expressions" is set to true (the default) then
* the the attribute value will be parsed to find any dynamic
* parts.
*/
allowExpressions: function(value) {
var attr = this.attr;
attr.allowExpressions = value;
},
/**
* By default, the Marko compiler maps an attribute
* to a property by removing all dashes from the attribute
* name and converting each character after a dash to
* an uppercase character (e.g. "my-attr" --> "myAttr").
*
* Setting "preserve-name" to true will prevent this from
* happening for the attribute.
*/
preserveName: function(value) {
var attr = this.attr;
attr.preserveName = value;
},
/**
* Declares an attribute as required. Currently, this is
* not enforced and is only used for documentation purposes.
*
* Example:
* "required": true
*/
required: function(value) {
var attr = this.attr;
attr.required = value === true;
},
/**
* This is the opposite of "preserve-name" and will result
* in dashes being removed from the attribute if set to true.
*/
removeDashes: function(value) {
var attr = this.attr;
attr.removeDashes = value === true;
},
/**
* The description of the attribute. Only used for documentation.
*/
description: function() {
},
/**
* The "set-flag" property allows a "flag" to be added to a Node instance
* at compile time if the attribute is found on the node. This is helpful
* if an attribute uses a pattern and a transformer wants to have a simple
* check to see if the Node has an attribute that matched the pattern.
*
* Example:
*
* "set-flag": "myCustomFlag"
*
* A Node instance can be checked if it has a flag set as shown below:
*
* if (node.hasFlag('myCustomFlag')) { ... }
*
*
*/
setFlag: function(value) {
var attr = this.attr;
attr.setFlag = value;
},
/**
* An attribute can be marked for ignore. Ignored attributes
* will be ignored during compilation.
*/
ignore: function(value) {
var attr = this.attr;
if (value === true) {

View File

@ -28,19 +28,71 @@ function removeDashes(str) {
});
}
function handleVar(tag, value, path) {
var nestedVariable;
function TagHandlers(tag, dirname, path) {
if (typeof value === 'string') {
nestedVariable = {
name: value
};
} else {
nestedVariable = {};
propertyHandlers(value, {
name: function(value) {
nestedVariable.name = value;
},
nameFromAttribute: function(value) {
nestedVariable.nameFromAttribute = value;
}
}, path);
if (!nestedVariable.name && !nestedVariable.nameFromAttribute) {
throw new Error('The "name" or "name-from-attribute" attribute is required for a nested variable');
}
}
tag.addNestedVariable(nestedVariable);
}
/**
* We load tag definition using this class. Properties in the taglib
* definition (which is just a JavaScript object with properties)
* are mapped to handler methods in an instance of this type.
*
* @param {Tag} tag The initially empty Tag instance that we populate
* @param {String} dirname The full file system path associated with the tag being loaded
* @param {String} path An informational path associated with this tag (used for error reporting)
*/
function TagHandlers(tag, dirname, path, taglib) {
this.tag = tag;
this.dirname = dirname;
this.path = path;
this.taglib = taglib;
}
TagHandlers.prototype = {
/**
* The tag name
* @param {String} value The tag name
*/
name: function(value) {
var tag = this.tag;
tag.name = value;
},
/**
* The path to the renderer JS module to use for this tag.
*
* NOTE: We use the equivalent of require.resolve to resolve the JS module
* and use the tag directory as the "from".
*
* @param {String} value The renderer path
*/
renderer: function(value) {
var tag = this.tag;
var dirname = this.dirname;
@ -48,6 +100,12 @@ TagHandlers.prototype = {
tag.renderer = path;
},
/**
* A tag can use a renderer or a template to do the rendering. If
* a template is provided then the value should be the path to the
* template to use to render the custom tag.
*/
template: function(value) {
var tag = this.tag;
var dirname = this.dirname;
@ -59,12 +117,32 @@ TagHandlers.prototype = {
tag.template = path;
},
/**
* An Object where each property maps to an attribute definition.
* The property key will be the attribute name and the property value
* will be the attribute definition. Example:
* {
* "attributes": {
* "foo": "string",
* "bar": "expression"
* }
* }
*/
attributes: function(value) {
var tag = this.tag;
var path = this.path;
handleAttributes(value, tag, path);
},
/**
* A custom tag can be mapped to a compile-time Node that gets
* added to the parsed Abstract Syntax Tree (AST). The Node can
* then generate custom JS code at compile time. The value
* should be a path to a JS module that gets resolved using the
* equivalent of require.resolve(path)
*/
nodeClass: function(value) {
var tag = this.tag;
var dirname = this.dirname;
@ -72,10 +150,22 @@ TagHandlers.prototype = {
var path = resolve(value, dirname);
tag.nodeClass = path;
},
/**
* If the "preserve-whitespace" property is set to true then
* all whitespace nested below the custom tag in a template
* will be stripped instead of going through the normal whitespace
* removal rules.
*/
preserveWhitespace: function(value) {
var tag = this.tag;
tag.preserveWhitespace = !!value;
},
/**
* If a custom tag has an associated transformer then the transformer
* will be called on the compile-time Node. The transformer can manipulate
* the AST using the DOM-like API to change how the code gets generated.
*/
transformer: function(value) {
var tag = this.tag;
var dirname = this.dirname;
@ -84,11 +174,19 @@ TagHandlers.prototype = {
var transformer = new Taglib.Transformer();
if (typeof value === 'string') {
// The value is a simple string type
// so treat the value as the path to the JS
// module for the transformer
value = {
path: value
};
}
/**
* The transformer is a complex type and we need
* to process each property to load the Transformer
* definition.
*/
propertyHandlers(value, {
path: function(value) {
var path = resolve(value, dirname);
@ -119,12 +217,48 @@ TagHandlers.prototype = {
tag.addTransformer(transformer);
},
/**
* The "var" property is used to declared nested variables that get
* added as JavaScript variables at compile time.
*
* Examples:
*
* "var": "myScopedVariable",
*
* "var": {
* "name": "myScopedVariable"
* }
*
* "var": {
* "name-from-attribute": "var"
* }
*/
'var': function(value) {
var tag = this.tag;
tag.addNestedVariable({
name: value
});
handleVar(this.tag, value, '"var" in tag ' + this.path);
},
/**
* The "vars" property is equivalent to the "var" property
* except that it expects an array of nested variables.
*/
vars: function(value) {
var tag = this.tag;
var self = this;
if (value) {
value.forEach(function(v, i) {
handleVar(tag, v, '"vars"[' + i + '] in tag ' + self.path);
});
}
},
/**
* The "body-function" property" allows the nested body content to be mapped
* to a function at compile time. The body function gets mapped to a property
* of the tag renderer at render time. The body function can have any number
* of parameters.
*
* Example:
* - "body-function": "_handleBody(param1, param2, param3)"
*/
bodyFunction: function(value) {
var tag = this.tag;
var parts = bodyFunctionRegExp.exec(value);
@ -149,44 +283,27 @@ TagHandlers.prototype = {
tag.setBodyFunction(functionName, params);
},
/**
* The "body-property" property can be used to map the body content
* to a String property on the renderer's input object.
*
* Example:
* "body-property": "label"
*/
bodyProperty: function(value) {
var tag = this.tag;
tag.setBodyProperty(value);
},
vars: function(value) {
var tag = this.tag;
if (value) {
value.forEach(function(v, i) {
var nestedVariable;
if (typeof v === 'string') {
nestedVariable = {
name: v
};
} else {
nestedVariable = {};
propertyHandlers(v, {
name: function(value) {
nestedVariable.name = value;
},
nameFromAttribute: function(value) {
nestedVariable.nameFromAttribute = value;
}
}, 'var at index ' + i);
if (!nestedVariable.name && !nestedVariable.nameFromAttribute) {
throw new Error('The "name" or "name-from-attribute" attribute is required for a nested variable');
}
}
tag.addNestedVariable(nestedVariable);
});
}
},
/**
* The "import-var" property can be used to add a property to the
* input object of the tag renderer whose value is determined by
* a JavaScript expression.
*
* Example:
* "import-var": {
* "myTargetProperty": "data.myCompileTimeJavaScriptExpression",
* }
*/
importVar: function(value) {
var tag = this.tag;
forEachEntry(value, function(varName, varValue) {
@ -211,12 +328,42 @@ TagHandlers.prototype = {
tag.addImportedVariable(importedVar);
});
},
/**
* The tag type.
*/
type: function(value) {
var tag = this.tag;
tag.type = value;
},
/**
* Declare a nested tag.
*
* Example:
* {
* ...
* "nested-tags": {
* "tab": {
* "target-property": "tabs",
* "isRepeated": true
* }
* }
* }
*/
nestedTags: function(value) {
var tagPath = this.path;
var taglib = this.taglib;
var dirname = this.dirname;
var tag = this.tag;
forEachEntry(value, function(nestedTagName, nestedTagDef) {
var nestedTag = loadTag(
nestedTagDef,
nestedTagName + ' of ' + tagPath,
taglib,
dirname);
nestedTag.name = nestedTagName;
tag.addNestedTag(nestedTag);
});
}
};
@ -239,7 +386,7 @@ function loadTag(tagProps, path, taglib, dirname) {
};
}
var tagHandlers = new TagHandlers(tag, dirname, path);
var tagHandlers = new TagHandlers(tag, dirname, path, taglib);
// We add a handler for any properties that didn't match
// one of the default property handlers. This is used to
@ -288,10 +435,12 @@ function loadTag(tagProps, path, taglib, dirname) {
tagProps[k] = value[k];
delete value[k];
} else {
// The property is not a shorthand attribute or shorthand
// tag so move it over to either the tag definition
// or the attribute definition or both the tag definition
// and attribute definition.
var propNameDashes = removeDashes(k);
if (loader.tagLoader.isSupportedProperty(propNameDashes) &&
loader.attributeLoader.isSupportedProperty(propNameDashes)) {
// Move over all of the properties that are associated with a tag
@ -365,6 +514,8 @@ function loadTag(tagProps, path, taglib, dirname) {
taglib,
dirname);
// We use the '[]' suffix to indicate that a nested tag
// can be repeated
var isNestedTagRepeated = false;
if (part.endsWith('[]')) {
isNestedTagRepeated = true;

View File

@ -55,6 +55,15 @@ function handleTag(taglibHandlers, tagName, path) {
taglib.addTag(tag);
}
/**
* We load a taglib definion using this class. Properties in the taglib
* definition (which is just a JavaScript object with properties)
* are mapped to handler methods in an instance of this type.
*
*
* @param {Taglib} taglib The initially empty Taglib instance that we will populate
* @param {String} path The file system path to the taglib that we are loading
*/
function TaglibHandlers(taglib, path) {
ok(taglib);
ok(path);
@ -66,12 +75,40 @@ function TaglibHandlers(taglib, path) {
TaglibHandlers.prototype = {
attributes: function(value) {
// The value of the "attributes" property will be an object
// where each property maps to an attribute definition. Since these
// attributes are on the taglib they will be "global" attribute
// defintions.
//
// The property key will be the attribute name and the property value
// will be the attribute definition. Example:
// {
// "attributes": {
// "foo": "string",
// "bar": "expression"
// }
// }
var taglib = this.taglib;
var path = this.path;
handleAttributes(value, taglib, path);
},
tags: function(tags) {
// The value of the "tags" property will be an object
// where each property maps to an attribute definition. The property
// key will be the tag name and the property value
// will be the tag definition. Example:
// {
// "tags": {
// "foo": {
// "attributes": { ... }
// },
// "bar": {
// "attributes": { ... }
// },
// }
// }
for (var tagName in tags) {
if (tags.hasOwnProperty(tagName)) {
handleTag(this, tagName, tags[tagName]);
@ -79,6 +116,12 @@ TaglibHandlers.prototype = {
}
},
tagsDir: function(dir) {
// The "tags-dir" property is used to supporting scanning
// of a directory to discover custom tags. Scanning a directory
// is a much simpler way for a developer to create custom tags.
// Only one tag is allowed per directory and the directory name
// corresponds to the tag name. We only search for directories
// one level deep.
var taglib = this.taglib;
var path = this.path;
var dirname = this.dirname;
@ -93,6 +136,14 @@ TaglibHandlers.prototype = {
},
taglibImports: function(imports) {
// The "taglib-imports" property allows another taglib to be imported
// into this taglib so that the tags defined in the imported taglib
// will be part of this taglib.
//
// NOTE: If a taglib import refers to a package.json file then we read
// the package.json file and automatically import *all* of the
// taglibs from the installed modules found in the "dependencies"
// section
var taglib = this.taglib;
var dirname = this.dirname;
@ -127,6 +178,8 @@ TaglibHandlers.prototype = {
},
textTransformer: function(value) {
// Marko allows a "text-transformer" to be registered. The provided
// text transformer will be called for any static text found in a template.
var taglib = this.taglib;
var path = this.path;
var dirname = this.dirname;
@ -150,8 +203,20 @@ TaglibHandlers.prototype = {
ok(transformer.path, '"path" is required for transformer');
taglib.addTextTransformer(transformer);
},
'*': function(name, value) {
}
};
exports.loadTaglib = function(path) {
var taglibProps = taglibReader.readTaglib(path);
var taglib = new Taglib(path);
taglib.addInputFile(path);
var taglibHandlers = new TaglibHandlers(taglib, path);
// We register a wildcard handler to handle "@my-attr" and "<my-tag>"
// properties (shorthand syntax)
taglibHandlers['*'] = function(name, value) {
var taglib = this.taglib;
var path = this.path;
@ -169,16 +234,7 @@ TaglibHandlers.prototype = {
} else {
return false;
}
}
};
exports.loadTaglib = function(path) {
var taglibProps = taglibReader.readTaglib(path);
var taglib = new Taglib(path);
taglib.addInputFile(path);
var taglibHandlers = new TaglibHandlers(taglib, path);
};
propertyHandlers(taglibProps, taglibHandlers, path);

View File

@ -12,10 +12,12 @@
"target-property": "className"
}
},
"@footer <footer>": {
"@class": {
"type": "string",
"target-property": "className"
"nested-tags": {
"footer": {
"@class": {
"type": "string",
"target-property": "className"
}
}
}
}