mirror of
https://github.com/marko-js/marko.git
synced 2025-12-08 19:26:05 +00:00
Fixes #61 Simplify parent/child relationships
This commit is contained in:
parent
374954254f
commit
183c3c62c4
@ -1,297 +0,0 @@
|
||||
/*
|
||||
* Copyright 2011 eBay Software Foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
var forEachEntry = require('raptor-util').forEachEntry;
|
||||
var ok = require('assert').ok;
|
||||
var makeClass = require('raptor-util').makeClass;
|
||||
|
||||
function inheritProps(sub, sup) {
|
||||
forEachEntry(sup, function (k, v) {
|
||||
if (!sub[k]) {
|
||||
sub[k] = v;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function Taglib(id) {
|
||||
ok(id, '"id" expected');
|
||||
this.id = id;
|
||||
this.dirname = null;
|
||||
this.tags = {};
|
||||
this.textTransformers = [];
|
||||
this.attributes = {};
|
||||
this.patternAttributes = [];
|
||||
this.inputFilesLookup = {};
|
||||
this.imports = null;
|
||||
}
|
||||
|
||||
Taglib.prototype = {
|
||||
|
||||
addInputFile: function(path) {
|
||||
this.inputFilesLookup[path] = true;
|
||||
},
|
||||
|
||||
getInputFiles: function() {
|
||||
return Object.keys(this.inputFilesLookup);
|
||||
},
|
||||
|
||||
addAttribute: function (attribute) {
|
||||
if (attribute.pattern) {
|
||||
this.patternAttributes.push(attribute);
|
||||
} else if (attribute.name) {
|
||||
this.attributes[attribute.name] = attribute;
|
||||
} else {
|
||||
throw new Error('Invalid attribute: ' + require('util').inspect(attribute));
|
||||
}
|
||||
},
|
||||
getAttribute: function (name) {
|
||||
var attribute = this.attributes[name];
|
||||
if (!attribute) {
|
||||
for (var i = 0, len = this.patternAttributes.length; i < len; i++) {
|
||||
var patternAttribute = this.patternAttributes[i];
|
||||
if (patternAttribute.pattern.test(name)) {
|
||||
attribute = patternAttribute;
|
||||
}
|
||||
}
|
||||
}
|
||||
return attribute;
|
||||
},
|
||||
addTag: function (tag) {
|
||||
ok(arguments.length === 1, 'Invalid args');
|
||||
ok(tag.name, '"tag.name" is required');
|
||||
this.tags[tag.name] = tag;
|
||||
tag.taglibId = this.id;
|
||||
},
|
||||
addTextTransformer: function (transformer) {
|
||||
this.textTransformers.push(transformer);
|
||||
},
|
||||
forEachTag: function (callback, thisObj) {
|
||||
forEachEntry(this.tags, function (key, tag) {
|
||||
callback.call(thisObj, tag);
|
||||
}, this);
|
||||
},
|
||||
|
||||
addImport: function(path) {
|
||||
if (!this.imports) {
|
||||
this.imports = [];
|
||||
}
|
||||
this.imports.push(path);
|
||||
}
|
||||
};
|
||||
|
||||
Taglib.Tag = makeClass({
|
||||
$init: function(taglib) {
|
||||
this.taglibId = taglib ? taglib.id : null;
|
||||
this.renderer = null;
|
||||
this.nodeClass = null;
|
||||
this.template = null;
|
||||
this.attributes = {};
|
||||
this.transformers = {};
|
||||
this.nestedVariables = null;
|
||||
this.importedVariables = null;
|
||||
this.patternAttributes = [];
|
||||
this.bodyFunction = null;
|
||||
},
|
||||
inheritFrom: function (superTag) {
|
||||
var subTag = this;
|
||||
/*
|
||||
* Have the sub tag inherit any properties from the super tag that are not in the sub tag
|
||||
*/
|
||||
forEachEntry(superTag, function (k, v) {
|
||||
if (subTag[k] === undefined) {
|
||||
subTag[k] = v;
|
||||
}
|
||||
});
|
||||
[
|
||||
'attributes',
|
||||
'transformers',
|
||||
'nestedVariables',
|
||||
'importedVariables',
|
||||
'bodyFunction'
|
||||
].forEach(function (propName) {
|
||||
inheritProps(subTag[propName], superTag[propName]);
|
||||
});
|
||||
subTag.patternAttributes = superTag.patternAttributes.concat(subTag.patternAttributes);
|
||||
},
|
||||
forEachVariable: function (callback, thisObj) {
|
||||
if (!this.nestedVariables) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.nestedVariables.vars.forEach(callback, thisObj);
|
||||
},
|
||||
forEachImportedVariable: function (callback, thisObj) {
|
||||
if (!this.importedVariables) {
|
||||
return;
|
||||
}
|
||||
|
||||
forEachEntry(this.importedVariables, function (key, importedVariable) {
|
||||
callback.call(thisObj, importedVariable);
|
||||
});
|
||||
},
|
||||
forEachTransformer: function (callback, thisObj) {
|
||||
forEachEntry(this.transformers, function (key, transformer) {
|
||||
callback.call(thisObj, transformer);
|
||||
});
|
||||
},
|
||||
hasTransformers: function () {
|
||||
/*jshint unused:false */
|
||||
for (var k in this.transformers) {
|
||||
if (this.transformers.hasOwnProperty(k)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
return false;
|
||||
},
|
||||
addAttribute: function (attr) {
|
||||
if (attr.pattern) {
|
||||
this.patternAttributes.push(attr);
|
||||
} else {
|
||||
if (attr.name === '*') {
|
||||
attr.dynamicAttribute = true;
|
||||
|
||||
if (attr.targetProperty === null || attr.targetProperty === '') {
|
||||
attr.targetProperty = null;
|
||||
|
||||
}
|
||||
else if (!attr.targetProperty) {
|
||||
attr.targetProperty = '*';
|
||||
}
|
||||
}
|
||||
|
||||
this.attributes[attr.name] = attr;
|
||||
}
|
||||
},
|
||||
toString: function () {
|
||||
return '[Tag: <' + this.name + '@' + this.taglibId + '>]';
|
||||
},
|
||||
forEachAttribute: function (callback, thisObj) {
|
||||
for (var attrName in this.attributes) {
|
||||
if (this.attributes.hasOwnProperty(attrName)) {
|
||||
callback.call(thisObj, this.attributes[attrName]);
|
||||
}
|
||||
}
|
||||
},
|
||||
addNestedVariable: function (nestedVariable) {
|
||||
if (!this.nestedVariables) {
|
||||
this.nestedVariables = {
|
||||
__noMerge: true,
|
||||
vars: []
|
||||
};
|
||||
}
|
||||
|
||||
this.nestedVariables.vars.push(nestedVariable);
|
||||
},
|
||||
addImportedVariable: function (importedVariable) {
|
||||
if (!this.importedVariables) {
|
||||
this.importedVariables = {};
|
||||
}
|
||||
var key = importedVariable.targetProperty;
|
||||
this.importedVariables[key] = importedVariable;
|
||||
},
|
||||
addTransformer: function (transformer) {
|
||||
var key = transformer.path;
|
||||
transformer.taglibId = this.taglibId;
|
||||
this.transformers[key] = transformer;
|
||||
},
|
||||
setBodyFunction: function(name, params) {
|
||||
this.bodyFunction = {
|
||||
__noMerge: true,
|
||||
name: name,
|
||||
params: params
|
||||
};
|
||||
},
|
||||
setBodyProperty: function(propertyName) {
|
||||
this.bodyProperty = propertyName;
|
||||
}
|
||||
});
|
||||
|
||||
Taglib.Attribute = makeClass({
|
||||
$init: function(name) {
|
||||
this.name = name;
|
||||
this.type = null;
|
||||
this.required = false;
|
||||
this.type = 'string';
|
||||
this.allowExpressions = true;
|
||||
this.setFlag = null;
|
||||
}
|
||||
});
|
||||
|
||||
Taglib.Property = makeClass({
|
||||
$init: function() {
|
||||
this.name = null;
|
||||
this.type = 'string';
|
||||
this.value = undefined;
|
||||
}
|
||||
});
|
||||
|
||||
Taglib.NestedVariable = makeClass({
|
||||
$init: function() {
|
||||
this.name = null;
|
||||
}
|
||||
});
|
||||
|
||||
Taglib.ImportedVariable = makeClass({
|
||||
$init: function() {
|
||||
this.targetProperty = null;
|
||||
this.expression = null;
|
||||
}
|
||||
});
|
||||
|
||||
var nextTransformerId = 0;
|
||||
|
||||
Taglib.Transformer = makeClass({
|
||||
$init: function() {
|
||||
this.id = nextTransformerId++;
|
||||
this.name = null;
|
||||
this.tag = null;
|
||||
this.path = null;
|
||||
this.priority = null;
|
||||
this._func = null;
|
||||
this.properties = {};
|
||||
},
|
||||
|
||||
getFunc: function () {
|
||||
if (!this.path) {
|
||||
throw new Error('Transformer path not defined for tag transformer (tag=' + this.tag + ')');
|
||||
}
|
||||
|
||||
if (!this._func) {
|
||||
var transformer = require(this.path);
|
||||
|
||||
if (typeof transformer === 'function') {
|
||||
if (transformer.prototype.process) {
|
||||
var Clazz = transformer;
|
||||
var instance = new Clazz();
|
||||
instance.id = this.id;
|
||||
this._func = instance.process.bind(instance);
|
||||
} else {
|
||||
this._func = transformer;
|
||||
}
|
||||
} else {
|
||||
this._func = transformer.process || transformer.transform;
|
||||
}
|
||||
}
|
||||
return this._func;
|
||||
},
|
||||
toString: function () {
|
||||
return '[Taglib.Transformer: ' + this.path + ']';
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = Taglib;
|
||||
12
compiler/taglibs/Taglib/Attribute.js
Normal file
12
compiler/taglibs/Taglib/Attribute.js
Normal file
@ -0,0 +1,12 @@
|
||||
var makeClass = require('raptor-util').makeClass;
|
||||
|
||||
module.exports = makeClass({
|
||||
$init: function(name) {
|
||||
this.name = name;
|
||||
this.type = null;
|
||||
this.required = false;
|
||||
this.type = 'string';
|
||||
this.allowExpressions = true;
|
||||
this.setFlag = null;
|
||||
}
|
||||
});
|
||||
8
compiler/taglibs/Taglib/ImportedVariable.js
Normal file
8
compiler/taglibs/Taglib/ImportedVariable.js
Normal file
@ -0,0 +1,8 @@
|
||||
var makeClass = require('raptor-util').makeClass;
|
||||
|
||||
module.exports = makeClass({
|
||||
$init: function() {
|
||||
this.targetProperty = null;
|
||||
this.expression = null;
|
||||
}
|
||||
});
|
||||
7
compiler/taglibs/Taglib/NestedVariable.js
Normal file
7
compiler/taglibs/Taglib/NestedVariable.js
Normal file
@ -0,0 +1,7 @@
|
||||
var makeClass = require('raptor-util').makeClass;
|
||||
|
||||
module.exports = makeClass({
|
||||
$init: function() {
|
||||
this.name = null;
|
||||
}
|
||||
});
|
||||
9
compiler/taglibs/Taglib/Property.js
Normal file
9
compiler/taglibs/Taglib/Property.js
Normal file
@ -0,0 +1,9 @@
|
||||
var makeClass = require('raptor-util').makeClass;
|
||||
|
||||
module.exports = makeClass({
|
||||
$init: function() {
|
||||
this.name = null;
|
||||
this.type = 'string';
|
||||
this.value = undefined;
|
||||
}
|
||||
});
|
||||
167
compiler/taglibs/Taglib/Tag.js
Normal file
167
compiler/taglibs/Taglib/Tag.js
Normal file
@ -0,0 +1,167 @@
|
||||
var makeClass = require('raptor-util').makeClass;
|
||||
var forEachEntry = require('raptor-util').forEachEntry;
|
||||
var ok = require('assert').ok;
|
||||
|
||||
function inheritProps(sub, sup) {
|
||||
forEachEntry(sup, function (k, v) {
|
||||
if (!sub[k]) {
|
||||
sub[k] = v;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = makeClass({
|
||||
$init: function(taglib) {
|
||||
this.taglibId = taglib ? taglib.id : null;
|
||||
this.renderer = null;
|
||||
this.nodeClass = null;
|
||||
this.template = null;
|
||||
this.attributes = {};
|
||||
this.transformers = {};
|
||||
this.nestedVariables = null;
|
||||
this.importedVariables = null;
|
||||
this.patternAttributes = [];
|
||||
this.bodyFunction = null;
|
||||
this.nestedTags = null;
|
||||
this.isRepeated = null;
|
||||
this.isNestedTag = false;
|
||||
this.parentTagName = null;
|
||||
this.type = null; // Only applicable for nested tags
|
||||
},
|
||||
inheritFrom: function (superTag) {
|
||||
var subTag = this;
|
||||
/*
|
||||
* Have the sub tag inherit any properties from the super tag that are not in the sub tag
|
||||
*/
|
||||
forEachEntry(superTag, function (k, v) {
|
||||
if (subTag[k] === undefined) {
|
||||
subTag[k] = v;
|
||||
}
|
||||
});
|
||||
[
|
||||
'attributes',
|
||||
'transformers',
|
||||
'nestedVariables',
|
||||
'importedVariables',
|
||||
'bodyFunction'
|
||||
].forEach(function (propName) {
|
||||
inheritProps(subTag[propName], superTag[propName]);
|
||||
});
|
||||
subTag.patternAttributes = superTag.patternAttributes.concat(subTag.patternAttributes);
|
||||
},
|
||||
forEachVariable: function (callback, thisObj) {
|
||||
if (!this.nestedVariables) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.nestedVariables.vars.forEach(callback, thisObj);
|
||||
},
|
||||
forEachImportedVariable: function (callback, thisObj) {
|
||||
if (!this.importedVariables) {
|
||||
return;
|
||||
}
|
||||
|
||||
forEachEntry(this.importedVariables, function (key, importedVariable) {
|
||||
callback.call(thisObj, importedVariable);
|
||||
});
|
||||
},
|
||||
forEachTransformer: function (callback, thisObj) {
|
||||
forEachEntry(this.transformers, function (key, transformer) {
|
||||
callback.call(thisObj, transformer);
|
||||
});
|
||||
},
|
||||
hasTransformers: function () {
|
||||
/*jshint unused:false */
|
||||
for (var k in this.transformers) {
|
||||
if (this.transformers.hasOwnProperty(k)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
return false;
|
||||
},
|
||||
addAttribute: function (attr) {
|
||||
if (attr.pattern) {
|
||||
this.patternAttributes.push(attr);
|
||||
} else {
|
||||
if (attr.name === '*') {
|
||||
attr.dynamicAttribute = true;
|
||||
|
||||
if (attr.targetProperty === null || attr.targetProperty === '') {
|
||||
attr.targetProperty = null;
|
||||
|
||||
}
|
||||
else if (!attr.targetProperty) {
|
||||
attr.targetProperty = '*';
|
||||
}
|
||||
}
|
||||
|
||||
this.attributes[attr.name] = attr;
|
||||
}
|
||||
},
|
||||
toString: function () {
|
||||
return '[Tag: <' + this.name + '@' + this.taglibId + '>]';
|
||||
},
|
||||
forEachAttribute: function (callback, thisObj) {
|
||||
for (var attrName in this.attributes) {
|
||||
if (this.attributes.hasOwnProperty(attrName)) {
|
||||
callback.call(thisObj, this.attributes[attrName]);
|
||||
}
|
||||
}
|
||||
},
|
||||
addNestedVariable: function (nestedVariable) {
|
||||
if (!this.nestedVariables) {
|
||||
this.nestedVariables = {
|
||||
__noMerge: true,
|
||||
vars: []
|
||||
};
|
||||
}
|
||||
|
||||
this.nestedVariables.vars.push(nestedVariable);
|
||||
},
|
||||
addImportedVariable: function (importedVariable) {
|
||||
if (!this.importedVariables) {
|
||||
this.importedVariables = {};
|
||||
}
|
||||
var key = importedVariable.targetProperty;
|
||||
this.importedVariables[key] = importedVariable;
|
||||
},
|
||||
addTransformer: function (transformer) {
|
||||
var key = transformer.path;
|
||||
transformer.taglibId = this.taglibId;
|
||||
this.transformers[key] = transformer;
|
||||
},
|
||||
setBodyFunction: function(name, params) {
|
||||
this.bodyFunction = {
|
||||
__noMerge: true,
|
||||
name: name,
|
||||
params: params
|
||||
};
|
||||
},
|
||||
setBodyProperty: function(propertyName) {
|
||||
this.bodyProperty = propertyName;
|
||||
},
|
||||
addNestedTag: function(nestedTag) {
|
||||
ok(nestedTag.name, '"nestedTag.name" is required');
|
||||
|
||||
if (!this.nestedTags) {
|
||||
this.nestedTags = {};
|
||||
}
|
||||
|
||||
nestedTag.isNestedTag = true;
|
||||
|
||||
this.nestedTags[nestedTag.name] = nestedTag;
|
||||
},
|
||||
forEachNestedTag: function (callback, thisObj) {
|
||||
if (!this.nestedTags) {
|
||||
return;
|
||||
}
|
||||
|
||||
forEachEntry(this.nestedTags, function (key, nestedTag) {
|
||||
callback.call(thisObj, nestedTag);
|
||||
});
|
||||
},
|
||||
hasNestedTags: function() {
|
||||
return this.nestedTags != null;
|
||||
}
|
||||
});
|
||||
42
compiler/taglibs/Taglib/Transformer.js
Normal file
42
compiler/taglibs/Taglib/Transformer.js
Normal file
@ -0,0 +1,42 @@
|
||||
var makeClass = require('raptor-util').makeClass;
|
||||
|
||||
var nextTransformerId = 0;
|
||||
|
||||
module.exports = makeClass({
|
||||
$init: function() {
|
||||
this.id = nextTransformerId++;
|
||||
this.name = null;
|
||||
this.tag = null;
|
||||
this.path = null;
|
||||
this.priority = null;
|
||||
this._func = null;
|
||||
this.properties = {};
|
||||
},
|
||||
|
||||
getFunc: function () {
|
||||
if (!this.path) {
|
||||
throw new Error('Transformer path not defined for tag transformer (tag=' + this.tag + ')');
|
||||
}
|
||||
|
||||
if (!this._func) {
|
||||
var transformer = require(this.path);
|
||||
|
||||
if (typeof transformer === 'function') {
|
||||
if (transformer.prototype.process) {
|
||||
var Clazz = transformer;
|
||||
var instance = new Clazz();
|
||||
instance.id = this.id;
|
||||
this._func = instance.process.bind(instance);
|
||||
} else {
|
||||
this._func = transformer;
|
||||
}
|
||||
} else {
|
||||
this._func = transformer.process || transformer.transform;
|
||||
}
|
||||
}
|
||||
return this._func;
|
||||
},
|
||||
toString: function () {
|
||||
return '[Taglib.Transformer: ' + this.path + ']';
|
||||
}
|
||||
});
|
||||
94
compiler/taglibs/Taglib/index.js
Normal file
94
compiler/taglibs/Taglib/index.js
Normal file
@ -0,0 +1,94 @@
|
||||
/*
|
||||
* Copyright 2011 eBay Software Foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
var forEachEntry = require('raptor-util').forEachEntry;
|
||||
var ok = require('assert').ok;
|
||||
|
||||
function Taglib(id) {
|
||||
ok(id, '"id" expected');
|
||||
this.id = id;
|
||||
this.dirname = null;
|
||||
this.tags = {};
|
||||
this.textTransformers = [];
|
||||
this.attributes = {};
|
||||
this.patternAttributes = [];
|
||||
this.inputFilesLookup = {};
|
||||
this.imports = null;
|
||||
}
|
||||
|
||||
Taglib.prototype = {
|
||||
|
||||
addInputFile: function(path) {
|
||||
this.inputFilesLookup[path] = true;
|
||||
},
|
||||
|
||||
getInputFiles: function() {
|
||||
return Object.keys(this.inputFilesLookup);
|
||||
},
|
||||
|
||||
addAttribute: function (attribute) {
|
||||
if (attribute.pattern) {
|
||||
this.patternAttributes.push(attribute);
|
||||
} else if (attribute.name) {
|
||||
this.attributes[attribute.name] = attribute;
|
||||
} else {
|
||||
throw new Error('Invalid attribute: ' + require('util').inspect(attribute));
|
||||
}
|
||||
},
|
||||
getAttribute: function (name) {
|
||||
var attribute = this.attributes[name];
|
||||
if (!attribute) {
|
||||
for (var i = 0, len = this.patternAttributes.length; i < len; i++) {
|
||||
var patternAttribute = this.patternAttributes[i];
|
||||
if (patternAttribute.pattern.test(name)) {
|
||||
attribute = patternAttribute;
|
||||
}
|
||||
}
|
||||
}
|
||||
return attribute;
|
||||
},
|
||||
addTag: function (tag) {
|
||||
ok(arguments.length === 1, 'Invalid args');
|
||||
ok(tag.name, '"tag.name" is required');
|
||||
this.tags[tag.name] = tag;
|
||||
tag.taglibId = this.id;
|
||||
},
|
||||
addTextTransformer: function (transformer) {
|
||||
this.textTransformers.push(transformer);
|
||||
},
|
||||
forEachTag: function (callback, thisObj) {
|
||||
forEachEntry(this.tags, function (key, tag) {
|
||||
callback.call(thisObj, tag);
|
||||
}, this);
|
||||
},
|
||||
|
||||
addImport: function(path) {
|
||||
if (!this.imports) {
|
||||
this.imports = [];
|
||||
}
|
||||
this.imports.push(path);
|
||||
}
|
||||
};
|
||||
|
||||
Taglib.Tag = require('./Tag');
|
||||
Taglib.Attribute = require('./Attribute');
|
||||
Taglib.Property = require('./Property');
|
||||
Taglib.NestedVariable = require('./NestedVariable');
|
||||
Taglib.ImportedVariable = require('./ImportedVariable');
|
||||
Taglib.Transformer = require('./Transformer');
|
||||
|
||||
module.exports = Taglib;
|
||||
@ -1,5 +1,7 @@
|
||||
var ok = require('assert').ok;
|
||||
var createError = require('raptor-util').createError;
|
||||
var Taglib = require('./Taglib');
|
||||
var extend = require('raptor-util/extend');
|
||||
|
||||
function transformerComparator(a, b) {
|
||||
a = a.priority;
|
||||
@ -66,7 +68,33 @@ function TaglibLookup() {
|
||||
}
|
||||
|
||||
TaglibLookup.prototype = {
|
||||
_mergeNestedTags: function(taglib) {
|
||||
var Tag = Taglib.Tag;
|
||||
// Loop over all of the nested tags and register a new custom tag
|
||||
// with the fully qualified name
|
||||
|
||||
var merged = this.merged;
|
||||
|
||||
function handleNestedTag(nestedTag, parentTagName) {
|
||||
var fullyQualifiedName = parentTagName + '.' + nestedTag.name;
|
||||
|
||||
// Create a clone of the nested tag since we need to add some new
|
||||
// properties
|
||||
var clonedNestedTag = new Tag();
|
||||
extend(clonedNestedTag ,nestedTag);
|
||||
// Record the fully qualified name of the parent tag that this
|
||||
// custom tag is associated with.
|
||||
clonedNestedTag.parentTagName = parentTagName;
|
||||
clonedNestedTag.name = fullyQualifiedName;
|
||||
merged.tags[fullyQualifiedName] = clonedNestedTag;
|
||||
}
|
||||
|
||||
taglib.forEachTag(function(tag) {
|
||||
tag.forEachNestedTag(function(nestedTag) {
|
||||
handleNestedTag(nestedTag, tag.name);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
addTaglib: function (taglib) {
|
||||
ok(taglib, '"taglib" is required');
|
||||
@ -79,6 +107,8 @@ TaglibLookup.prototype = {
|
||||
this.taglibsById[taglib.id] = taglib;
|
||||
|
||||
merge(this.merged, taglib);
|
||||
|
||||
this._mergeNestedTags(taglib);
|
||||
},
|
||||
|
||||
getTag: function (element) {
|
||||
|
||||
@ -1,525 +0,0 @@
|
||||
var fs ;
|
||||
var req = require;
|
||||
|
||||
try {
|
||||
fs = req('fs');
|
||||
} catch(e) {
|
||||
|
||||
}
|
||||
|
||||
var ok = require('assert').ok;
|
||||
var nodePath = require('path');
|
||||
var Taglib = require('./Taglib');
|
||||
var cache = {};
|
||||
var forEachEntry = require('raptor-util').forEachEntry;
|
||||
var raptorRegexp = require('raptor-regexp');
|
||||
var tagDefFromCode = require('./tag-def-from-code');
|
||||
var resolve = require('../util/resolve'); // NOTE: different implementation for browser
|
||||
var propertyHandlers = require('property-handlers');
|
||||
var jsonminify = require('jsonminify');
|
||||
|
||||
var safeVarName = /^[A-Za-z_$][A-Za-z0-9_]*$/;
|
||||
var bodyFunctionRegExp = /^([A-Za-z_$][A-Za-z0-9_]*)(?:\(([^)]*)\))?$/;
|
||||
|
||||
function exists(path) {
|
||||
try {
|
||||
require.resolve(path);
|
||||
return true;
|
||||
} catch(e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function createDefaultTagDef() {
|
||||
return {
|
||||
attributes: {
|
||||
'*': {
|
||||
type: 'string',
|
||||
targetProperty: null,
|
||||
preserveName: false
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function buildAttribute(attr, attrProps, path) {
|
||||
propertyHandlers(attrProps, {
|
||||
type: function(value) {
|
||||
attr.type = value;
|
||||
},
|
||||
targetProperty: function(value) {
|
||||
attr.targetProperty = value;
|
||||
},
|
||||
defaultValue: function(value) {
|
||||
attr.defaultValue = value;
|
||||
},
|
||||
pattern: function(value) {
|
||||
if (value === true) {
|
||||
var patternRegExp = raptorRegexp.simple(attr.name);
|
||||
attr.pattern = patternRegExp;
|
||||
}
|
||||
},
|
||||
allowExpressions: function(value) {
|
||||
attr.allowExpressions = value;
|
||||
},
|
||||
preserveName: function(value) {
|
||||
attr.preserveName = value;
|
||||
},
|
||||
required: function(value) {
|
||||
attr.required = value === true;
|
||||
},
|
||||
removeDashes: function(value) {
|
||||
attr.removeDashes = value === true;
|
||||
},
|
||||
description: function() {
|
||||
|
||||
},
|
||||
setFlag: function(value) {
|
||||
attr.setFlag = value;
|
||||
},
|
||||
ignore: function(value) {
|
||||
if (value === true) {
|
||||
attr.ignore = true;
|
||||
}
|
||||
}
|
||||
}, path);
|
||||
|
||||
return attr;
|
||||
}
|
||||
|
||||
function handleAttributes(value, parent, path) {
|
||||
forEachEntry(value, function(attrName, attrProps) {
|
||||
var attr = new Taglib.Attribute(attrName);
|
||||
|
||||
if (attrProps == null) {
|
||||
attrProps = {
|
||||
type: 'string'
|
||||
};
|
||||
} else if (typeof attrProps === 'string') {
|
||||
attrProps = {
|
||||
type: attrProps
|
||||
};
|
||||
}
|
||||
|
||||
buildAttribute(attr, attrProps, '"' + attrName + '" attribute as part of ' + path);
|
||||
|
||||
parent.addAttribute(attr);
|
||||
});
|
||||
}
|
||||
|
||||
function buildTag(tagObject, path, taglib, dirname) {
|
||||
ok(tagObject);
|
||||
ok(typeof path === 'string');
|
||||
ok(taglib);
|
||||
ok(typeof dirname === 'string');
|
||||
|
||||
var tag = new Taglib.Tag(taglib);
|
||||
|
||||
if (tagObject.attributes == null) {
|
||||
// allow any attributes if no attributes are declared
|
||||
tagObject.attributes = {
|
||||
'*': 'string'
|
||||
};
|
||||
}
|
||||
|
||||
propertyHandlers(tagObject, {
|
||||
name: function(value) {
|
||||
tag.name = value;
|
||||
},
|
||||
|
||||
renderer: function(value) {
|
||||
var path = resolve(value, dirname);
|
||||
|
||||
tag.renderer = path;
|
||||
},
|
||||
template: function(value) {
|
||||
var path = nodePath.resolve(dirname, value);
|
||||
if (!exists(path)) {
|
||||
throw new Error('Template at path "' + path + '" does not exist.');
|
||||
}
|
||||
|
||||
tag.template = path;
|
||||
},
|
||||
attributes: function(value) {
|
||||
handleAttributes(value, tag, path);
|
||||
},
|
||||
nodeClass: function(value) {
|
||||
var path = resolve(value, dirname);
|
||||
|
||||
tag.nodeClass = path;
|
||||
},
|
||||
preserveWhitespace: function(value) {
|
||||
tag.preserveWhitespace = !!value;
|
||||
},
|
||||
transformer: function(value) {
|
||||
var transformer = new Taglib.Transformer();
|
||||
|
||||
if (typeof value === 'string') {
|
||||
value = {
|
||||
path: value
|
||||
};
|
||||
}
|
||||
|
||||
propertyHandlers(value, {
|
||||
path: function(value) {
|
||||
var path = resolve(value, dirname);
|
||||
transformer.path = path;
|
||||
},
|
||||
|
||||
priority: function(value) {
|
||||
transformer.priority = value;
|
||||
},
|
||||
|
||||
name: function(value) {
|
||||
transformer.name = value;
|
||||
},
|
||||
|
||||
properties: function(value) {
|
||||
var properties = transformer.properties || (transformer.properties = {});
|
||||
for (var k in value) {
|
||||
if (value.hasOwnProperty(k)) {
|
||||
properties[k] = value[k];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}, 'transformer in ' + path);
|
||||
|
||||
ok(transformer.path, '"path" is required for transformer');
|
||||
|
||||
tag.addTransformer(transformer);
|
||||
},
|
||||
|
||||
'var': function(value) {
|
||||
tag.addNestedVariable({
|
||||
name: value
|
||||
});
|
||||
},
|
||||
bodyFunction: function(value) {
|
||||
var parts = bodyFunctionRegExp.exec(value);
|
||||
if (!parts) {
|
||||
throw new Error('Invalid value of "' + value + '" for "body-function". Expected value to be of the following form: <function-name>([param1, param2, ...])');
|
||||
}
|
||||
|
||||
var functionName = parts[1];
|
||||
var params = parts[2];
|
||||
if (params) {
|
||||
params = params.trim().split(/\s*,\s*/);
|
||||
for (var i=0; i<params.length; i++) {
|
||||
if (params[i].length === 0) {
|
||||
throw new Error('Invalid parameters for body-function with value of "' + value + '"');
|
||||
} else if (!safeVarName.test(params[i])) {
|
||||
throw new Error('Invalid parameter name of "' + params[i] + '" for body-function with value of "' + value + '"');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
params = [];
|
||||
}
|
||||
|
||||
tag.setBodyFunction(functionName, params);
|
||||
},
|
||||
bodyProperty: function(value) {
|
||||
tag.setBodyProperty(value);
|
||||
},
|
||||
vars: function(value) {
|
||||
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);
|
||||
});
|
||||
}
|
||||
},
|
||||
importVar: function(value) {
|
||||
forEachEntry(value, function(varName, varValue) {
|
||||
var importedVar = {
|
||||
targetProperty: varName
|
||||
};
|
||||
|
||||
var expression = varValue;
|
||||
|
||||
if (!expression) {
|
||||
expression = varName;
|
||||
}
|
||||
else if (typeof expression === 'object') {
|
||||
expression = expression.expression;
|
||||
}
|
||||
|
||||
if (!expression) {
|
||||
throw new Error('Invalid "import-var": ' + require('util').inspect(varValue));
|
||||
}
|
||||
|
||||
importedVar.expression = expression;
|
||||
tag.addImportedVariable(importedVar);
|
||||
});
|
||||
}
|
||||
}, path);
|
||||
|
||||
return tag;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {String} tagsConfigPath path to tag definition file
|
||||
* @param {String} tagsConfigDirname path to directory of tags config file (should be path.dirname(tagsConfigPath))
|
||||
* @param {String|Object} dir the path to directory to scan
|
||||
* @param {String} taglib the taglib that is being loaded
|
||||
*/
|
||||
function scanTagsDir(tagsConfigPath, tagsConfigDirname, dir, taglib) {
|
||||
var prefix;
|
||||
|
||||
if (typeof dir === 'object') {
|
||||
prefix = dir.prefix;
|
||||
dir = dir.path;
|
||||
}
|
||||
|
||||
if (prefix == null) {
|
||||
// no prefix by default
|
||||
prefix = '';
|
||||
}
|
||||
|
||||
dir = nodePath.resolve(tagsConfigDirname, dir);
|
||||
var children = fs.readdirSync(dir);
|
||||
var rendererJSFile;
|
||||
|
||||
for (var i=0, len=children.length; i<len; i++) {
|
||||
rendererJSFile = null;
|
||||
var childFilename = children[i];
|
||||
if (childFilename === 'node_modules') {
|
||||
continue;
|
||||
}
|
||||
|
||||
var tagName = prefix + childFilename;
|
||||
var tagDirname = nodePath.join(dir, childFilename);
|
||||
var tagFile = nodePath.join(dir, childFilename, 'marko-tag.json');
|
||||
var tag = null;
|
||||
var rendererFile = nodePath.join(dir, childFilename, 'renderer.js');
|
||||
var indexFile = nodePath.join(dir, childFilename, 'index.js');
|
||||
var templateFile = nodePath.join(dir, childFilename, 'template.marko');
|
||||
var tagDef = null;
|
||||
|
||||
// Record dependencies so that we can check if a template is up-to-date
|
||||
taglib.addInputFile(tagFile);
|
||||
taglib.addInputFile(rendererFile);
|
||||
|
||||
if (fs.existsSync(tagFile)) {
|
||||
// marko-tag.json exists in the directory, use that as the tag definition
|
||||
tagDef = JSON.parse(jsonminify(fs.readFileSync(tagFile, {encoding: 'utf8'})));
|
||||
if (!tagDef.renderer && !tagDef.template) {
|
||||
if (fs.existsSync(rendererFile)) {
|
||||
tagDef.renderer = rendererFile;
|
||||
} else if (fs.existsSync(indexFile)) {
|
||||
tagDef.renderer = indexFile;
|
||||
} else if (fs.existsSync(templateFile)) {
|
||||
tagDef.template = templateFile;
|
||||
} else if (fs.existsSync(templateFile + ".html")) {
|
||||
tagDef.template = templateFile + ".html";
|
||||
} else {
|
||||
throw new Error('Invalid tag file: ' + tagFile + '. Neither a renderer or a template was found for tag.');
|
||||
}
|
||||
}
|
||||
|
||||
tag = buildTag(tagDef, tagsConfigPath, taglib, tagDirname);
|
||||
tag.name = tag.name || tagName;
|
||||
taglib.addTag(tag);
|
||||
} else {
|
||||
// marko-tag.json does *not* exist... checking for a 'renderer.js'
|
||||
|
||||
|
||||
if (fs.existsSync(rendererFile)) {
|
||||
rendererJSFile = rendererFile;
|
||||
} else if (fs.existsSync(indexFile)) {
|
||||
rendererJSFile = indexFile;
|
||||
} else {
|
||||
var exTemplateFile;
|
||||
if (fs.existsSync(templateFile)) {
|
||||
exTemplateFile = templateFile;
|
||||
}
|
||||
else if (fs.existsSync(templateFile + ".html")){
|
||||
exTemplateFile = templateFile + ".html";
|
||||
}
|
||||
if(exTemplateFile){
|
||||
var templateCode = fs.readFileSync(exTemplateFile, {encoding: 'utf8'});
|
||||
tagDef = tagDefFromCode.extractTagDef(templateCode);
|
||||
if (!tagDef) {
|
||||
tagDef = createDefaultTagDef();
|
||||
}
|
||||
|
||||
tagDef.template = exTemplateFile;
|
||||
}
|
||||
}
|
||||
|
||||
if (rendererJSFile) {
|
||||
var rendererCode = fs.readFileSync(rendererJSFile, {encoding: 'utf8'});
|
||||
tagDef = tagDefFromCode.extractTagDef(rendererCode);
|
||||
if (!tagDef) {
|
||||
tagDef = createDefaultTagDef();
|
||||
}
|
||||
|
||||
tagDef.renderer = rendererJSFile;
|
||||
tag = buildTag(tagDef, tagsConfigPath, taglib, tagDirname);
|
||||
tag.name = tagName;
|
||||
taglib.addTag(tag);
|
||||
}
|
||||
|
||||
if (tagDef) {
|
||||
tag = buildTag(tagDef, tagsConfigPath, taglib, tagDirname);
|
||||
tag.name = tagName;
|
||||
taglib.addTag(tag);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function load(path) {
|
||||
if (cache[path]) {
|
||||
return cache[path];
|
||||
}
|
||||
|
||||
var taglibObject;
|
||||
|
||||
try {
|
||||
taglibObject = require(path);
|
||||
} catch(e) {
|
||||
throw new Error('Unable to parse taglib JSON at path "' + path + '". Exception: ' + e);
|
||||
}
|
||||
|
||||
var taglib = new Taglib(path);
|
||||
taglib.addInputFile(path);
|
||||
var dirname = nodePath.dirname(path);
|
||||
|
||||
propertyHandlers(taglibObject, {
|
||||
attributes: function(value) {
|
||||
handleAttributes(value, taglib, path);
|
||||
},
|
||||
tags: function(tags) {
|
||||
forEachEntry(tags, function(tagName, path) {
|
||||
ok(path, 'Invalid tag definition for "' + tagName + '"');
|
||||
var tagObject;
|
||||
|
||||
var tagDirname;
|
||||
|
||||
if (typeof path === 'string') {
|
||||
path = nodePath.resolve(dirname, path);
|
||||
taglib.addInputFile(path);
|
||||
|
||||
tagDirname = nodePath.dirname(path);
|
||||
if (!exists(path)) {
|
||||
throw new Error('Tag at path "' + path + '" does not exist. Taglib: ' + taglib.id);
|
||||
}
|
||||
|
||||
try {
|
||||
tagObject = require(path);
|
||||
} catch(e) {
|
||||
throw new Error('Unable to parse tag JSON for tag at path "' + path + '"');
|
||||
}
|
||||
} else {
|
||||
tagDirname = dirname; // Tag is in the same taglib file
|
||||
tagObject = path;
|
||||
path = '<' + tagName + '> tag in ' + taglib.id;
|
||||
}
|
||||
|
||||
|
||||
var tag = buildTag(tagObject, path, taglib, tagDirname);
|
||||
if (tag.name === undefined) {
|
||||
tag.name = tagName;
|
||||
}
|
||||
taglib.addTag(tag);
|
||||
});
|
||||
},
|
||||
tagsDir: function(dir) {
|
||||
|
||||
if (Array.isArray(dir)) {
|
||||
for (var i = 0; i < dir.length; i++) {
|
||||
scanTagsDir(path, dirname, dir[i], taglib);
|
||||
}
|
||||
} else {
|
||||
scanTagsDir(path, dirname, dir, taglib);
|
||||
}
|
||||
},
|
||||
|
||||
taglibImports: function(imports) {
|
||||
if (imports && Array.isArray(imports)) {
|
||||
for (var i=0; i<imports.length; i++) {
|
||||
var curImport = imports[i];
|
||||
if (typeof curImport === 'string') {
|
||||
var basename = nodePath.basename(curImport);
|
||||
if (basename === 'package.json') {
|
||||
var packagePath = resolve(curImport, dirname);
|
||||
var pkg = require(packagePath);
|
||||
var dependencies = pkg.dependencies;
|
||||
if (dependencies) {
|
||||
var dependencyNames = Object.keys(dependencies);
|
||||
for (var j=0; j<dependencyNames.length; j++) {
|
||||
var dependencyName = dependencyNames[j];
|
||||
var importPath;
|
||||
|
||||
try {
|
||||
importPath = require('resolve-from')(dirname, dependencyName + '/marko-taglib.json');
|
||||
} catch(e) {}
|
||||
|
||||
if (importPath) {
|
||||
taglib.addImport(importPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
textTransformer: function(value) {
|
||||
var transformer = new Taglib.Transformer();
|
||||
|
||||
if (typeof value === 'string') {
|
||||
value = {
|
||||
path: value
|
||||
};
|
||||
}
|
||||
|
||||
propertyHandlers(value, {
|
||||
path: function(value) {
|
||||
var path = resolve(value, dirname);
|
||||
transformer.path = path;
|
||||
}
|
||||
|
||||
}, 'text-transformer in ' + path);
|
||||
|
||||
ok(transformer.path, '"path" is required for transformer');
|
||||
|
||||
taglib.addTextTransformer(transformer);
|
||||
}
|
||||
}, path);
|
||||
|
||||
taglib.id = taglib.path = path;
|
||||
|
||||
cache[path] = taglib;
|
||||
|
||||
return taglib;
|
||||
}
|
||||
|
||||
exports.load = load;
|
||||
16
compiler/taglibs/taglib-loader/handleAttributes.js
Normal file
16
compiler/taglibs/taglib-loader/handleAttributes.js
Normal file
@ -0,0 +1,16 @@
|
||||
var ok = require('assert').ok;
|
||||
var forEachEntry = require('raptor-util').forEachEntry;
|
||||
var loader = require('./loader');
|
||||
|
||||
module.exports = function handleAttributes(value, parent, path) {
|
||||
ok(parent);
|
||||
|
||||
forEachEntry(value, function(attrName, attrProps) {
|
||||
var attr = loader.attributeLoader.loadAttribute(
|
||||
attrName,
|
||||
attrProps,
|
||||
'"' + attrName + '" attribute as part of ' + path);
|
||||
|
||||
parent.addAttribute(attr);
|
||||
});
|
||||
};
|
||||
20
compiler/taglibs/taglib-loader/index.js
Normal file
20
compiler/taglibs/taglib-loader/index.js
Normal file
@ -0,0 +1,20 @@
|
||||
require('raptor-polyfill/string/startsWith');
|
||||
|
||||
var loader = require('./loader');
|
||||
|
||||
|
||||
var cache = {};
|
||||
|
||||
function load(path) {
|
||||
if (cache[path]) {
|
||||
return cache[path];
|
||||
}
|
||||
|
||||
var taglib = loader.taglibLoader.loadTaglib(path);
|
||||
|
||||
cache[path] = taglib;
|
||||
|
||||
return taglib;
|
||||
}
|
||||
|
||||
exports.load = load;
|
||||
83
compiler/taglibs/taglib-loader/loader-attribute.js
Normal file
83
compiler/taglibs/taglib-loader/loader-attribute.js
Normal file
@ -0,0 +1,83 @@
|
||||
var assert = require('assert');
|
||||
var raptorRegexp = require('raptor-regexp');
|
||||
var propertyHandlers = require('property-handlers');
|
||||
var Taglib = require('../Taglib');
|
||||
|
||||
function AttrHandlers(attr){
|
||||
assert.ok(attr);
|
||||
assert.equal(typeof attr, 'object');
|
||||
this.attr = attr;
|
||||
}
|
||||
|
||||
AttrHandlers.prototype = {
|
||||
type: function(value) {
|
||||
var attr = this.attr;
|
||||
attr.type = value;
|
||||
},
|
||||
targetProperty: function(value) {
|
||||
var attr = this.attr;
|
||||
attr.targetProperty = value;
|
||||
},
|
||||
defaultValue: function(value) {
|
||||
var attr = this.attr;
|
||||
attr.defaultValue = value;
|
||||
},
|
||||
pattern: function(value) {
|
||||
var attr = this.attr;
|
||||
if (value === true) {
|
||||
var patternRegExp = raptorRegexp.simple(attr.name);
|
||||
attr.pattern = patternRegExp;
|
||||
}
|
||||
},
|
||||
allowExpressions: function(value) {
|
||||
var attr = this.attr;
|
||||
attr.allowExpressions = value;
|
||||
},
|
||||
preserveName: function(value) {
|
||||
var attr = this.attr;
|
||||
attr.preserveName = value;
|
||||
},
|
||||
required: function(value) {
|
||||
var attr = this.attr;
|
||||
attr.required = value === true;
|
||||
},
|
||||
removeDashes: function(value) {
|
||||
var attr = this.attr;
|
||||
attr.removeDashes = value === true;
|
||||
},
|
||||
description: function() {
|
||||
|
||||
},
|
||||
setFlag: function(value) {
|
||||
var attr = this.attr;
|
||||
attr.setFlag = value;
|
||||
},
|
||||
ignore: function(value) {
|
||||
var attr = this.attr;
|
||||
if (value === true) {
|
||||
attr.ignore = true;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
exports.isSupportedProperty = function(name) {
|
||||
return AttrHandlers.prototype.hasOwnProperty(name);
|
||||
};
|
||||
|
||||
exports.loadAttribute = function loadAttribute(attrName, attrProps, path) {
|
||||
var attr = new Taglib.Attribute(attrName);
|
||||
|
||||
if (attrProps == null) {
|
||||
attrProps = {
|
||||
type: 'string'
|
||||
};
|
||||
} else if (typeof attrProps === 'string') {
|
||||
attrProps = {
|
||||
type: attrProps
|
||||
};
|
||||
}
|
||||
|
||||
var attrHandlers = new AttrHandlers(attr);
|
||||
propertyHandlers(attrProps, attrHandlers, path);
|
||||
return attr;
|
||||
};
|
||||
392
compiler/taglibs/taglib-loader/loader-tag.js
Normal file
392
compiler/taglibs/taglib-loader/loader-tag.js
Normal file
@ -0,0 +1,392 @@
|
||||
var ok = require('assert').ok;
|
||||
var Taglib = require('../Taglib');
|
||||
var propertyHandlers = require('property-handlers');
|
||||
var isObjectEmpty = require('raptor-util/isObjectEmpty');
|
||||
var nodePath = require('path');
|
||||
var resolve = require('../../util/resolve'); // NOTE: different implementation for browser
|
||||
var ok = require('assert').ok;
|
||||
var bodyFunctionRegExp = /^([A-Za-z_$][A-Za-z0-9_]*)(?:\(([^)]*)\))?$/;
|
||||
var safeVarName = /^[A-Za-z_$][A-Za-z0-9_]*$/;
|
||||
var handleAttributes = require('./handleAttributes');
|
||||
var Taglib = require('../Taglib');
|
||||
var propertyHandlers = require('property-handlers');
|
||||
var forEachEntry = require('raptor-util').forEachEntry;
|
||||
var loader = require('./loader');
|
||||
|
||||
function exists(path) {
|
||||
try {
|
||||
require.resolve(path);
|
||||
return true;
|
||||
} catch(e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function removeDashes(str) {
|
||||
return str.replace(/-([a-z])/g, function (match, lower) {
|
||||
return lower.toUpperCase();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function TagHandlers(tag, dirname, path) {
|
||||
this.tag = tag;
|
||||
this.dirname = dirname;
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
TagHandlers.prototype = {
|
||||
name: function(value) {
|
||||
var tag = this.tag;
|
||||
tag.name = value;
|
||||
},
|
||||
|
||||
renderer: function(value) {
|
||||
var tag = this.tag;
|
||||
var dirname = this.dirname;
|
||||
var path = resolve(value, dirname);
|
||||
|
||||
tag.renderer = path;
|
||||
},
|
||||
template: function(value) {
|
||||
var tag = this.tag;
|
||||
var dirname = this.dirname;
|
||||
|
||||
var path = nodePath.resolve(dirname, value);
|
||||
if (!exists(path)) {
|
||||
throw new Error('Template at path "' + path + '" does not exist.');
|
||||
}
|
||||
|
||||
tag.template = path;
|
||||
},
|
||||
attributes: function(value) {
|
||||
var tag = this.tag;
|
||||
var path = this.path;
|
||||
|
||||
handleAttributes(value, tag, path);
|
||||
},
|
||||
nodeClass: function(value) {
|
||||
var tag = this.tag;
|
||||
var dirname = this.dirname;
|
||||
|
||||
var path = resolve(value, dirname);
|
||||
tag.nodeClass = path;
|
||||
},
|
||||
preserveWhitespace: function(value) {
|
||||
var tag = this.tag;
|
||||
tag.preserveWhitespace = !!value;
|
||||
},
|
||||
transformer: function(value) {
|
||||
var tag = this.tag;
|
||||
var dirname = this.dirname;
|
||||
var path = this.path;
|
||||
|
||||
var transformer = new Taglib.Transformer();
|
||||
|
||||
if (typeof value === 'string') {
|
||||
value = {
|
||||
path: value
|
||||
};
|
||||
}
|
||||
|
||||
propertyHandlers(value, {
|
||||
path: function(value) {
|
||||
var path = resolve(value, dirname);
|
||||
transformer.path = path;
|
||||
},
|
||||
|
||||
priority: function(value) {
|
||||
transformer.priority = value;
|
||||
},
|
||||
|
||||
name: function(value) {
|
||||
transformer.name = value;
|
||||
},
|
||||
|
||||
properties: function(value) {
|
||||
var properties = transformer.properties || (transformer.properties = {});
|
||||
for (var k in value) {
|
||||
if (value.hasOwnProperty(k)) {
|
||||
properties[k] = value[k];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}, 'transformer in ' + path);
|
||||
|
||||
ok(transformer.path, '"path" is required for transformer');
|
||||
|
||||
tag.addTransformer(transformer);
|
||||
},
|
||||
|
||||
'var': function(value) {
|
||||
var tag = this.tag;
|
||||
tag.addNestedVariable({
|
||||
name: value
|
||||
});
|
||||
},
|
||||
bodyFunction: function(value) {
|
||||
var tag = this.tag;
|
||||
var parts = bodyFunctionRegExp.exec(value);
|
||||
if (!parts) {
|
||||
throw new Error('Invalid value of "' + value + '" for "body-function". Expected value to be of the following form: <function-name>([param1, param2, ...])');
|
||||
}
|
||||
|
||||
var functionName = parts[1];
|
||||
var params = parts[2];
|
||||
if (params) {
|
||||
params = params.trim().split(/\s*,\s*/);
|
||||
for (var i=0; i<params.length; i++) {
|
||||
if (params[i].length === 0) {
|
||||
throw new Error('Invalid parameters for body-function with value of "' + value + '"');
|
||||
} else if (!safeVarName.test(params[i])) {
|
||||
throw new Error('Invalid parameter name of "' + params[i] + '" for body-function with value of "' + value + '"');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
params = [];
|
||||
}
|
||||
|
||||
tag.setBodyFunction(functionName, params);
|
||||
},
|
||||
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);
|
||||
});
|
||||
}
|
||||
},
|
||||
importVar: function(value) {
|
||||
var tag = this.tag;
|
||||
forEachEntry(value, function(varName, varValue) {
|
||||
var importedVar = {
|
||||
targetProperty: varName
|
||||
};
|
||||
|
||||
var expression = varValue;
|
||||
|
||||
if (!expression) {
|
||||
expression = varName;
|
||||
}
|
||||
else if (typeof expression === 'object') {
|
||||
expression = expression.expression;
|
||||
}
|
||||
|
||||
if (!expression) {
|
||||
throw new Error('Invalid "import-var": ' + require('util').inspect(varValue));
|
||||
}
|
||||
|
||||
importedVar.expression = expression;
|
||||
tag.addImportedVariable(importedVar);
|
||||
});
|
||||
},
|
||||
type: function(value) {
|
||||
var tag = this.tag;
|
||||
tag.type = value;
|
||||
},
|
||||
nestedTags: function(value) {
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
exports.isSupportedProperty = function(name) {
|
||||
return TagHandlers.prototype.hasOwnProperty(name);
|
||||
};
|
||||
|
||||
function loadTag(tagProps, path, taglib, dirname) {
|
||||
ok(tagProps);
|
||||
ok(typeof path === 'string');
|
||||
ok(taglib);
|
||||
ok(typeof dirname === 'string');
|
||||
|
||||
var tag = new Taglib.Tag(taglib);
|
||||
|
||||
if (tagProps.attributes == null) {
|
||||
// allow any attributes if no attributes are declared
|
||||
tagProps.attributes = {
|
||||
'*': 'string'
|
||||
};
|
||||
}
|
||||
|
||||
var tagHandlers = new TagHandlers(tag, dirname, path);
|
||||
|
||||
// We add a handler for any properties that didn't match
|
||||
// one of the default property handlers. This is used to
|
||||
// match properties in the form of "@attr_name" or
|
||||
// "<nested_tag_name>"
|
||||
tagHandlers['*'] = function(name, value) {
|
||||
var parts = name.split(/\s+|\s+[,]\s+/);
|
||||
|
||||
var i;
|
||||
var part;
|
||||
|
||||
var hasNestedTag = false;
|
||||
var hasAttr = false;
|
||||
var nestedTagTargetProperty = null;
|
||||
|
||||
// We do one pass to figure out if there is an
|
||||
// attribute or nested tag or both
|
||||
for (i=0; i<parts.length; i++) {
|
||||
part = parts[i];
|
||||
if (part.startsWith('@')) {
|
||||
hasAttr = true;
|
||||
|
||||
if (i === 0) {
|
||||
// Use the first attribute value as the name of the target property
|
||||
nestedTagTargetProperty = part.substring(1);
|
||||
}
|
||||
} else if (part.startsWith('<')) {
|
||||
hasNestedTag = true;
|
||||
} else {
|
||||
// Unmatched property that is not an attribute or a
|
||||
// nested tag
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
var attrProps = {};
|
||||
var tagProps = {};
|
||||
var k;
|
||||
|
||||
if (value != null && typeof value === 'object') {
|
||||
for (k in value) {
|
||||
if (value.hasOwnProperty(k)) {
|
||||
if (k.startsWith('@') || k.startsWith('<')) {
|
||||
// Move over all of the attributes and nested tags
|
||||
// to the tag definition.
|
||||
tagProps[k] = value[k];
|
||||
delete value[k];
|
||||
} else {
|
||||
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
|
||||
// and attribute
|
||||
tagProps[k] = value[k];
|
||||
attrProps[k] = value[k];
|
||||
delete value[k];
|
||||
} else if (loader.tagLoader.isSupportedProperty(propNameDashes)) {
|
||||
// Move over all of the properties that are associated with a tag
|
||||
tagProps[k] = value[k];
|
||||
delete value[k];
|
||||
} else if (loader.attributeLoader.isSupportedProperty(propNameDashes)) {
|
||||
// Move over all of the properties that are associated with an attr
|
||||
attrProps[k] = value[k];
|
||||
delete value[k];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If there are any left over properties then something is wrong
|
||||
// with the user's taglib.
|
||||
if (!isObjectEmpty(value)) {
|
||||
throw new Error('Unsupported properties of [' +
|
||||
Object.keys(value).join(', ') +
|
||||
'] for "' + name + '" in "' + path + '"');
|
||||
}
|
||||
|
||||
var type = attrProps.type;
|
||||
if (!type && hasAttr && hasNestedTag) {
|
||||
// If we have an attribute and a nested tag then default
|
||||
// the attribute type to "expression"
|
||||
attrProps.type = 'expression';
|
||||
}
|
||||
} else if (typeof value === 'string') {
|
||||
if (hasNestedTag && hasAttr) {
|
||||
tagProps = attrProps = {
|
||||
type: value
|
||||
};
|
||||
} else if (hasNestedTag) {
|
||||
tagProps = {
|
||||
type: value
|
||||
};
|
||||
} else {
|
||||
attrProps = {
|
||||
type: value
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Now that we have separated out attribute properties and tag properties
|
||||
// we need to create the actual attributes and nested tags
|
||||
for (i=0; i<parts.length; i++) {
|
||||
part = parts[i];
|
||||
if (part.startsWith('@')) {
|
||||
// This is a shorthand attribute
|
||||
var attrName = part.substring(1);
|
||||
|
||||
var attr = loader.attributeLoader.loadAttribute(
|
||||
attrName,
|
||||
attrProps,
|
||||
'"' + attrName + '" attribute as part of ' + path);
|
||||
|
||||
tag.addAttribute(attr);
|
||||
} else if (part.startsWith('<')) {
|
||||
|
||||
// This is a shorthand nested tag
|
||||
var nestedTag = loadTag(
|
||||
tagProps,
|
||||
name + ' of ' + path,
|
||||
taglib,
|
||||
dirname);
|
||||
|
||||
var isNestedTagRepeated = false;
|
||||
if (part.endsWith('[]')) {
|
||||
isNestedTagRepeated = true;
|
||||
part = part.slice(0, -2);
|
||||
}
|
||||
|
||||
var nestedTagName = part.substring(1, part.length-1);
|
||||
nestedTag.name = nestedTagName;
|
||||
nestedTag.isRepeated = isNestedTagRepeated;
|
||||
// Use the name of the attribute as the target property unless
|
||||
// this target property was explicitly provided
|
||||
nestedTag.targetProperty = attrProps.targetProperty || nestedTagTargetProperty;
|
||||
tag.addNestedTag(nestedTag);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
propertyHandlers(tagProps, tagHandlers, path);
|
||||
|
||||
return tag;
|
||||
}
|
||||
|
||||
exports.loadTag = loadTag;
|
||||
187
compiler/taglibs/taglib-loader/loader-taglib.js
Normal file
187
compiler/taglibs/taglib-loader/loader-taglib.js
Normal file
@ -0,0 +1,187 @@
|
||||
var ok = require('assert').ok;
|
||||
var nodePath = require('path');
|
||||
var handleAttributes = require('./handleAttributes');
|
||||
var scanTagsDir = require('./scanTagsDir');
|
||||
var resolve = require('../../util/resolve'); // NOTE: different implementation for browser
|
||||
var propertyHandlers = require('property-handlers');
|
||||
var Taglib = require('../Taglib');
|
||||
var taglibReader = require('./taglib-reader');
|
||||
var loader = require('./loader');
|
||||
|
||||
function exists(path) {
|
||||
try {
|
||||
require.resolve(path);
|
||||
return true;
|
||||
} catch(e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function handleTag(taglibHandlers, tagName, path) {
|
||||
var taglib = taglibHandlers.taglib;
|
||||
var dirname = taglibHandlers.dirname;
|
||||
|
||||
ok(path, 'Invalid tag definition for "' + tagName + '"');
|
||||
|
||||
var tagObject;
|
||||
|
||||
var tagDirname;
|
||||
|
||||
if (typeof path === 'string') {
|
||||
path = nodePath.resolve(dirname, path);
|
||||
taglib.addInputFile(path);
|
||||
|
||||
tagDirname = nodePath.dirname(path);
|
||||
if (!exists(path)) {
|
||||
throw new Error('Tag at path "' + path + '" does not exist. Taglib: ' + taglib.id);
|
||||
}
|
||||
|
||||
try {
|
||||
tagObject = require(path);
|
||||
} catch(e) {
|
||||
throw new Error('Unable to parse tag JSON for tag at path "' + path + '"');
|
||||
}
|
||||
} else {
|
||||
tagDirname = dirname; // Tag is in the same taglib file
|
||||
tagObject = path;
|
||||
path = '<' + tagName + '> tag in ' + taglib.id;
|
||||
}
|
||||
|
||||
|
||||
var tag = loader.tagLoader.loadTag(tagObject, path, taglib, tagDirname);
|
||||
if (tag.name === undefined) {
|
||||
tag.name = tagName;
|
||||
}
|
||||
taglib.addTag(tag);
|
||||
}
|
||||
|
||||
function TaglibHandlers(taglib, path) {
|
||||
ok(taglib);
|
||||
ok(path);
|
||||
|
||||
this.taglib = taglib;
|
||||
this.path = path;
|
||||
this.dirname = nodePath.dirname(path);
|
||||
}
|
||||
|
||||
TaglibHandlers.prototype = {
|
||||
attributes: function(value) {
|
||||
var taglib = this.taglib;
|
||||
var path = this.path;
|
||||
|
||||
handleAttributes(value, taglib, path);
|
||||
},
|
||||
tags: function(tags) {
|
||||
for (var tagName in tags) {
|
||||
if (tags.hasOwnProperty(tagName)) {
|
||||
handleTag(this, tagName, tags[tagName]);
|
||||
}
|
||||
}
|
||||
},
|
||||
tagsDir: function(dir) {
|
||||
var taglib = this.taglib;
|
||||
var path = this.path;
|
||||
var dirname = this.dirname;
|
||||
|
||||
if (Array.isArray(dir)) {
|
||||
for (var i = 0; i < dir.length; i++) {
|
||||
scanTagsDir(path, dirname, dir[i], taglib);
|
||||
}
|
||||
} else {
|
||||
scanTagsDir(path, dirname, dir, taglib);
|
||||
}
|
||||
},
|
||||
|
||||
taglibImports: function(imports) {
|
||||
var taglib = this.taglib;
|
||||
var dirname = this.dirname;
|
||||
|
||||
if (imports && Array.isArray(imports)) {
|
||||
for (var i=0; i<imports.length; i++) {
|
||||
var curImport = imports[i];
|
||||
if (typeof curImport === 'string') {
|
||||
var basename = nodePath.basename(curImport);
|
||||
if (basename === 'package.json') {
|
||||
var packagePath = resolve(curImport, dirname);
|
||||
var pkg = require(packagePath);
|
||||
var dependencies = pkg.dependencies;
|
||||
if (dependencies) {
|
||||
var dependencyNames = Object.keys(dependencies);
|
||||
for (var j=0; j<dependencyNames.length; j++) {
|
||||
var dependencyName = dependencyNames[j];
|
||||
var importPath;
|
||||
|
||||
try {
|
||||
importPath = require('resolve-from')(dirname, dependencyName + '/marko-taglib.json');
|
||||
} catch(e) {}
|
||||
|
||||
if (importPath) {
|
||||
taglib.addImport(importPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
textTransformer: function(value) {
|
||||
var taglib = this.taglib;
|
||||
var path = this.path;
|
||||
var dirname = this.dirname;
|
||||
|
||||
var transformer = new Taglib.Transformer();
|
||||
|
||||
if (typeof value === 'string') {
|
||||
value = {
|
||||
path: value
|
||||
};
|
||||
}
|
||||
|
||||
propertyHandlers(value, {
|
||||
path: function(value) {
|
||||
var path = resolve(value, dirname);
|
||||
transformer.path = path;
|
||||
}
|
||||
|
||||
}, 'text-transformer in ' + path);
|
||||
|
||||
ok(transformer.path, '"path" is required for transformer');
|
||||
|
||||
taglib.addTextTransformer(transformer);
|
||||
},
|
||||
'*': function(name, value) {
|
||||
var taglib = this.taglib;
|
||||
var path = this.path;
|
||||
|
||||
if (name.startsWith('<')) {
|
||||
handleTag(this, name.slice(1, -1), value);
|
||||
} else if (name.startsWith('@')) {
|
||||
var attrName = name.substring(1);
|
||||
|
||||
var attr = loader.attributeLoader.loadAttribute(
|
||||
attrName,
|
||||
value,
|
||||
'"' + attrName + '" attribute as part of ' + path);
|
||||
|
||||
taglib.addAttribute(attr);
|
||||
} 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);
|
||||
|
||||
taglib.id = taglib.path = path;
|
||||
return taglib;
|
||||
};
|
||||
3
compiler/taglibs/taglib-loader/loader.js
Normal file
3
compiler/taglibs/taglib-loader/loader.js
Normal file
@ -0,0 +1,3 @@
|
||||
exports.taglibLoader = require('./loader-taglib');
|
||||
exports.tagLoader = require('./loader-tag');
|
||||
exports.attributeLoader = require('./loader-attribute');
|
||||
6
compiler/taglibs/taglib-loader/package.json
Normal file
6
compiler/taglibs/taglib-loader/package.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"browser": {
|
||||
"./scanTagsDir.js": "./scanTagsDir-browser.js",
|
||||
"./taglib-reader.js": "./taglib-reader-browser.js"
|
||||
}
|
||||
}
|
||||
3
compiler/taglibs/taglib-loader/scanTagsDir-browser.js
Normal file
3
compiler/taglibs/taglib-loader/scanTagsDir-browser.js
Normal file
@ -0,0 +1,3 @@
|
||||
module.exports = function scanTagsDir() {
|
||||
// no-op in the browser
|
||||
};
|
||||
128
compiler/taglibs/taglib-loader/scanTagsDir.js
Normal file
128
compiler/taglibs/taglib-loader/scanTagsDir.js
Normal file
@ -0,0 +1,128 @@
|
||||
var nodePath = require('path');
|
||||
var fs = require('fs');
|
||||
var jsonminify = require('jsonminify');
|
||||
var tagDefFromCode = require('./tag-def-from-code');
|
||||
var loader = require('./loader');
|
||||
|
||||
function createDefaultTagDef() {
|
||||
return {
|
||||
attributes: {
|
||||
'*': {
|
||||
type: 'string',
|
||||
targetProperty: null,
|
||||
preserveName: false
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param {String} tagsConfigPath path to tag definition file
|
||||
* @param {String} tagsConfigDirname path to directory of tags config file (should be path.dirname(tagsConfigPath))
|
||||
* @param {String|Object} dir the path to directory to scan
|
||||
* @param {String} taglib the taglib that is being loaded
|
||||
*/
|
||||
module.exports = function scanTagsDir(tagsConfigPath, tagsConfigDirname, dir, taglib) {
|
||||
var prefix;
|
||||
|
||||
if (typeof dir === 'object') {
|
||||
prefix = dir.prefix;
|
||||
dir = dir.path;
|
||||
}
|
||||
|
||||
if (prefix == null) {
|
||||
// no prefix by default
|
||||
prefix = '';
|
||||
}
|
||||
|
||||
dir = nodePath.resolve(tagsConfigDirname, dir);
|
||||
var children = fs.readdirSync(dir);
|
||||
var rendererJSFile;
|
||||
|
||||
for (var i=0, len=children.length; i<len; i++) {
|
||||
rendererJSFile = null;
|
||||
var childFilename = children[i];
|
||||
if (childFilename === 'node_modules') {
|
||||
continue;
|
||||
}
|
||||
|
||||
var tagName = prefix + childFilename;
|
||||
var tagDirname = nodePath.join(dir, childFilename);
|
||||
var tagFile = nodePath.join(dir, childFilename, 'marko-tag.json');
|
||||
var tag = null;
|
||||
var rendererFile = nodePath.join(dir, childFilename, 'renderer.js');
|
||||
var indexFile = nodePath.join(dir, childFilename, 'index.js');
|
||||
var templateFile = nodePath.join(dir, childFilename, 'template.marko');
|
||||
var tagDef = null;
|
||||
|
||||
// Record dependencies so that we can check if a template is up-to-date
|
||||
taglib.addInputFile(tagFile);
|
||||
taglib.addInputFile(rendererFile);
|
||||
|
||||
if (fs.existsSync(tagFile)) {
|
||||
// marko-tag.json exists in the directory, use that as the tag definition
|
||||
tagDef = JSON.parse(jsonminify(fs.readFileSync(tagFile, {encoding: 'utf8'})));
|
||||
if (!tagDef.renderer && !tagDef.template) {
|
||||
if (fs.existsSync(rendererFile)) {
|
||||
tagDef.renderer = rendererFile;
|
||||
} else if (fs.existsSync(indexFile)) {
|
||||
tagDef.renderer = indexFile;
|
||||
} else if (fs.existsSync(templateFile)) {
|
||||
tagDef.template = templateFile;
|
||||
} else if (fs.existsSync(templateFile + ".html")) {
|
||||
tagDef.template = templateFile + ".html";
|
||||
} else {
|
||||
throw new Error('Invalid tag file: ' + tagFile + '. Neither a renderer or a template was found for tag.');
|
||||
}
|
||||
}
|
||||
|
||||
tag = loader.tagLoader.loadTag(tagDef, tagsConfigPath, taglib, tagDirname);
|
||||
tag.name = tag.name || tagName;
|
||||
taglib.addTag(tag);
|
||||
} else {
|
||||
// marko-tag.json does *not* exist... checking for a 'renderer.js'
|
||||
if (fs.existsSync(rendererFile)) {
|
||||
rendererJSFile = rendererFile;
|
||||
} else if (fs.existsSync(indexFile)) {
|
||||
rendererJSFile = indexFile;
|
||||
} else {
|
||||
var exTemplateFile;
|
||||
if (fs.existsSync(templateFile)) {
|
||||
exTemplateFile = templateFile;
|
||||
}
|
||||
else if (fs.existsSync(templateFile + ".html")){
|
||||
exTemplateFile = templateFile + ".html";
|
||||
}
|
||||
if(exTemplateFile){
|
||||
var templateCode = fs.readFileSync(exTemplateFile, {encoding: 'utf8'});
|
||||
tagDef = tagDefFromCode.extractTagDef(templateCode);
|
||||
if (!tagDef) {
|
||||
tagDef = createDefaultTagDef();
|
||||
}
|
||||
|
||||
tagDef.template = exTemplateFile;
|
||||
}
|
||||
}
|
||||
|
||||
if (rendererJSFile) {
|
||||
var rendererCode = fs.readFileSync(rendererJSFile, {encoding: 'utf8'});
|
||||
tagDef = tagDefFromCode.extractTagDef(rendererCode);
|
||||
if (!tagDef) {
|
||||
tagDef = createDefaultTagDef();
|
||||
}
|
||||
|
||||
tagDef.renderer = rendererJSFile;
|
||||
tag = loader.tagLoader.loadTag(tagDef, tagsConfigPath, taglib, tagDirname);
|
||||
tag.name = tagName;
|
||||
taglib.addTag(tag);
|
||||
}
|
||||
|
||||
if (tagDef) {
|
||||
tag = loader.tagLoader.loadTag(tagDef, tagsConfigPath, taglib, tagDirname);
|
||||
tag.name = tagName;
|
||||
taglib.addTag(tag);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
11
compiler/taglibs/taglib-loader/taglib-reader-browser.js
Normal file
11
compiler/taglibs/taglib-loader/taglib-reader-browser.js
Normal file
@ -0,0 +1,11 @@
|
||||
exports.readTaglib = function (path) {
|
||||
var taglibProps;
|
||||
|
||||
try {
|
||||
taglibProps = require(path);
|
||||
} catch(e) {
|
||||
throw new Error('Unable to parse taglib JSON at path "' + path + '". Exception: ' + e);
|
||||
}
|
||||
|
||||
return taglibProps;
|
||||
};
|
||||
13
compiler/taglibs/taglib-loader/taglib-reader.js
Normal file
13
compiler/taglibs/taglib-loader/taglib-reader.js
Normal file
@ -0,0 +1,13 @@
|
||||
var fs = require('fs');
|
||||
var jsonminify = require('jsonminify');
|
||||
|
||||
exports.readTaglib = function (path) {
|
||||
var json = fs.readFileSync(path, 'utf8');
|
||||
|
||||
try {
|
||||
var taglibProps = JSON.parse(jsonminify(json));
|
||||
return taglibProps;
|
||||
} catch(e) {
|
||||
throw new Error('Unable to parse taglib at path "' + path + '". Error: ' + e);
|
||||
}
|
||||
};
|
||||
@ -14,7 +14,9 @@
|
||||
"scripts": {
|
||||
"test": "node_modules/.bin/mocha --ui bdd --reporter spec ./test && node_modules/.bin/jshint compiler/ runtime/ taglibs/",
|
||||
"test-fast": "node_modules/.bin/mocha --ui bdd --reporter spec ./test/render-test",
|
||||
"test-async": "node_modules/.bin/mocha --ui bdd --reporter spec ./test/render-async-test"
|
||||
"test-async": "node_modules/.bin/mocha --ui bdd --reporter spec ./test/render-async-test",
|
||||
"test-taglib-loader": "node_modules/.bin/mocha --ui bdd --reporter spec ./test/taglib-loader-test",
|
||||
"jshint": "node_modules/.bin/jshint compiler/ runtime/ taglibs/"
|
||||
},
|
||||
"author": "Patrick Steele-Idem <pnidem@gmail.com>",
|
||||
"maintainers": [
|
||||
|
||||
@ -155,30 +155,61 @@ module.exports = {
|
||||
/**
|
||||
* Invoke a tag handler render function
|
||||
*/
|
||||
t: function (out, renderFunc, input, body, hasOutParam) {
|
||||
t: function (out, renderFunc, input, renderBody, options) {
|
||||
if (!input) {
|
||||
input = {};
|
||||
}
|
||||
|
||||
if (body) {
|
||||
input.renderBody = body;
|
||||
input.invokeBody = function() {
|
||||
if (!WARNED_INVOKE_BODY) {
|
||||
WARNED_INVOKE_BODY = 1;
|
||||
logger.warn('invokeBody(...) deprecated. Use renderBody(out) instead.', new Error().stack);
|
||||
}
|
||||
var hasOutParam;
|
||||
var targetProperty;
|
||||
var parent;
|
||||
var hasNestedTags;
|
||||
var isRepeated;
|
||||
|
||||
if (!hasOutParam) {
|
||||
var args = arrayFromArguments(arguments);
|
||||
args.unshift(out);
|
||||
body.apply(this, args);
|
||||
} else {
|
||||
body.apply(this, arguments);
|
||||
}
|
||||
};
|
||||
if (options) {
|
||||
hasOutParam = options.hasOutParam;
|
||||
parent = options.parent;
|
||||
targetProperty = options.targetProperty;
|
||||
hasNestedTags = options.hasNestedTags;
|
||||
isRepeated = options.isRepeated;
|
||||
}
|
||||
|
||||
renderFunc(input, out);
|
||||
if (renderBody) {
|
||||
if (hasNestedTags) {
|
||||
renderBody(out, input);
|
||||
} else {
|
||||
input.renderBody = renderBody;
|
||||
input.invokeBody = function() {
|
||||
if (!WARNED_INVOKE_BODY) {
|
||||
WARNED_INVOKE_BODY = 1;
|
||||
logger.warn('invokeBody(...) deprecated. Use renderBody(out) instead.', new Error().stack);
|
||||
}
|
||||
|
||||
if (!hasOutParam) {
|
||||
var args = arrayFromArguments(arguments);
|
||||
args.unshift(out);
|
||||
renderBody.apply(this, args);
|
||||
} else {
|
||||
renderBody.apply(this, arguments);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (renderFunc) {
|
||||
renderFunc(input, out);
|
||||
} else if (targetProperty) {
|
||||
if (isRepeated) {
|
||||
var existingArray = parent[targetProperty];
|
||||
if (existingArray) {
|
||||
existingArray.push(input);
|
||||
} else {
|
||||
parent[targetProperty] = [input];
|
||||
}
|
||||
} else {
|
||||
parent[targetProperty] = input;
|
||||
}
|
||||
}
|
||||
},
|
||||
c: function (out, func) {
|
||||
var output = out.captureString(func);
|
||||
|
||||
@ -70,6 +70,28 @@ function getPropsStr(props, template) {
|
||||
return '{}';
|
||||
}
|
||||
}
|
||||
|
||||
function getNextNestedTagVarName(template) {
|
||||
if (template.data.nextNestedTagId == null) {
|
||||
template.data.nextNestedTagId = 0;
|
||||
}
|
||||
|
||||
return '__nestedTagInput' + (template.data.nextNestedTagId++);
|
||||
}
|
||||
|
||||
function getNestedTagParentNode(nestedTagNode, tag) {
|
||||
var parentTagName = tag.parentTagName;
|
||||
|
||||
var currentNode = nestedTagNode.parentNode;
|
||||
while (currentNode) {
|
||||
if (currentNode.localName === parentTagName) {
|
||||
return currentNode;
|
||||
}
|
||||
|
||||
currentNode = currentNode.parentNode;
|
||||
}
|
||||
}
|
||||
|
||||
function TagHandlerNode(tag) {
|
||||
if (!this.nodeType) {
|
||||
TagHandlerNode.$super.call(this);
|
||||
@ -112,13 +134,41 @@ TagHandlerNode.prototype = {
|
||||
doGenerateCode: function (template) {
|
||||
template.addStaticVar('__renderer', '__helpers.r');
|
||||
var _this = this;
|
||||
var rendererPath = template.getRequirePath(this.tag.renderer); // Resolve a path to the renderer relative to the directory of the template
|
||||
var handlerVar = addHandlerVar(template, rendererPath);
|
||||
var tagHelperVar = template.addStaticVar('__tag', '__helpers.t');
|
||||
var bodyFunction = this.tag.bodyFunction;
|
||||
var bodyProperty = this.tag.bodyProperty;
|
||||
var tag = this.tag;
|
||||
|
||||
this.tag.forEachImportedVariable(function (importedVariable) {
|
||||
var rendererPath;
|
||||
var handlerVar;
|
||||
|
||||
if (tag.renderer) {
|
||||
rendererPath = template.getRequirePath(this.tag.renderer); // Resolve a path to the renderer relative to the directory of the template
|
||||
handlerVar = addHandlerVar(template, rendererPath);
|
||||
}
|
||||
|
||||
|
||||
var bodyFunction = tag.bodyFunction;
|
||||
var bodyProperty = tag.bodyProperty;
|
||||
var isNestedTag = tag.isNestedTag === true;
|
||||
var hasNestedTags = tag.hasNestedTags();
|
||||
var tagHelperVar = template.addStaticVar('__tag', '__helpers.t');
|
||||
|
||||
var nestedTagVar;
|
||||
var nestedTagParentNode = null;
|
||||
|
||||
if (isNestedTag) {
|
||||
nestedTagParentNode = getNestedTagParentNode(this, tag);
|
||||
if (nestedTagParentNode == null) {
|
||||
this.addError('Parent tag of <' + tag.parentTagName + '> not found in template.');
|
||||
return;
|
||||
}
|
||||
|
||||
nestedTagVar = nestedTagParentNode.data.nestedTagVar;
|
||||
}
|
||||
|
||||
if (hasNestedTags) {
|
||||
nestedTagVar = this.data.nestedTagVar = getNextNestedTagVarName(template);
|
||||
}
|
||||
|
||||
tag.forEachImportedVariable(function (importedVariable) {
|
||||
this.setProperty(importedVariable.targetProperty, template.makeExpression(importedVariable.expression));
|
||||
}, this);
|
||||
|
||||
@ -138,7 +188,7 @@ TagHandlerNode.prototype = {
|
||||
|
||||
|
||||
var variableNames = [];
|
||||
_this.tag.forEachVariable(function (nestedVar) {
|
||||
tag.forEachVariable(function (nestedVar) {
|
||||
var varName;
|
||||
if (nestedVar.nameFromAttribute) {
|
||||
var possibleNameAttributes = nestedVar.nameFromAttribute.split(/\s+or\s+|\s*,\s*/i);
|
||||
@ -178,7 +228,7 @@ TagHandlerNode.prototype = {
|
||||
|
||||
template.functionCall(tagHelperVar, function () {
|
||||
template.code('out,\n').indent(function () {
|
||||
template.line(handlerVar + ',').indent();
|
||||
template.line((handlerVar ? handlerVar : 'null') + ',').indent();
|
||||
|
||||
if (_this.dynamicAttributes) {
|
||||
template.indent(function() {
|
||||
@ -215,32 +265,56 @@ TagHandlerNode.prototype = {
|
||||
|
||||
template.code(propsCode);
|
||||
|
||||
if (_this.hasChildren() && !_this.tag.bodyFunction) {
|
||||
var bodyParams = [];
|
||||
var hasOutParam = false;
|
||||
var hasOutParam = false;
|
||||
|
||||
variableNames.forEach(function (varName) {
|
||||
if (varName === 'out') {
|
||||
hasOutParam = true;
|
||||
}
|
||||
bodyParams.push(varName);
|
||||
});
|
||||
if (_this.hasChildren() && !tag.bodyFunction) {
|
||||
var bodyParams = [];
|
||||
|
||||
|
||||
if (hasNestedTags) {
|
||||
bodyParams.push(nestedTagVar);
|
||||
} else {
|
||||
variableNames.forEach(function (varName) {
|
||||
if (varName === 'out') {
|
||||
hasOutParam = true;
|
||||
}
|
||||
bodyParams.push(varName);
|
||||
});
|
||||
}
|
||||
|
||||
var params;
|
||||
|
||||
if (hasOutParam) {
|
||||
params = bodyParams.join(',');
|
||||
} else {
|
||||
params = 'out' + (bodyParams.length ? ',' + bodyParams.join(',') : '');
|
||||
params = 'out' + (bodyParams.length ? ', ' + bodyParams.join(', ') : '');
|
||||
}
|
||||
|
||||
template.code(',\n').line('function(' + params + ') {').indent(function () {
|
||||
_this.generateCodeForChildren(template);
|
||||
}).indent().code('}');
|
||||
}
|
||||
|
||||
if (hasNestedTags || isNestedTag || hasOutParam) {
|
||||
var options = [];
|
||||
|
||||
if (hasNestedTags) {
|
||||
options.push('hasNestedTags: 1');
|
||||
}
|
||||
|
||||
if (hasOutParam) {
|
||||
template.code(',\n').code(template.indentStr() + '1');
|
||||
options.push('hasOutParam: 1');
|
||||
}
|
||||
|
||||
if (isNestedTag) {
|
||||
options.push('targetProperty: ' + JSON.stringify(tag.targetProperty));
|
||||
options.push('parent: ' + nestedTagVar);
|
||||
if (tag.isRepeated) {
|
||||
options.push('isRepeated: 1');
|
||||
}
|
||||
}
|
||||
|
||||
template.code(',\n').code(template.indentStr() + '{ ' + options.join(', ') + ' }');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@ -269,24 +269,23 @@ module.exports = function transform(node, compiler, template) {
|
||||
node.setPreserveWhitespace(true);
|
||||
}
|
||||
|
||||
if (tag.renderer || tag.template) {
|
||||
if (tag.renderer || tag.isNestedTag) {
|
||||
shouldRemoveAttr = false;
|
||||
|
||||
if (tag.renderer) {
|
||||
//Instead of compiling as a static XML element, we'll
|
||||
//make the node render as a tag handler node so that
|
||||
//writes code that invokes the handler
|
||||
TagHandlerNode.convertNode(node, tag);
|
||||
if (inputAttr) {
|
||||
node.setInputExpression(template.makeExpression(inputAttr));
|
||||
}
|
||||
} else {
|
||||
var templatePath = compiler.getRequirePath(tag.template);
|
||||
// The tag is mapped to a template that will be used to perform
|
||||
// the rendering so convert the node into a "IncludeNode" that can
|
||||
// be used to include the output of rendering a template
|
||||
IncludeNode.convertNode(node, templatePath);
|
||||
//Instead of compiling as a static XML element, we'll
|
||||
//make the node render as a tag handler node so that
|
||||
//writes code that invokes the handler
|
||||
TagHandlerNode.convertNode(node, tag);
|
||||
if (inputAttr) {
|
||||
node.setInputExpression(template.makeExpression(inputAttr));
|
||||
}
|
||||
} else if (tag.template) {
|
||||
shouldRemoveAttr = false;
|
||||
var templatePath = compiler.getRequirePath(tag.template);
|
||||
// The tag is mapped to a template that will be used to perform
|
||||
// the rendering so convert the node into a "IncludeNode" that can
|
||||
// be used to include the output of rendering a template
|
||||
IncludeNode.convertNode(node, templatePath);
|
||||
} else if (tag.nodeClass) {
|
||||
shouldRemoveAttr = false;
|
||||
|
||||
|
||||
2
test/fixtures/marko-taglib.json
vendored
2
test/fixtures/marko-taglib.json
vendored
@ -116,6 +116,8 @@
|
||||
"template": "./taglib/test-circular-template-b/template.marko"
|
||||
}
|
||||
},
|
||||
"<test-nested-tags-tabs>": "./taglib/test-nested-tags-tabs/marko-tag.json",
|
||||
"<test-nested-tags-overlay>": "./taglib/test-nested-tags-overlay/marko-tag.json",
|
||||
"tags-dir": "./taglib/scanned-tags",
|
||||
"taglib-imports": ["./package.json"]
|
||||
}
|
||||
25
test/fixtures/taglib-shorthand/marko-taglib.json
vendored
Normal file
25
test/fixtures/taglib-shorthand/marko-taglib.json
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
{
|
||||
"<shorthand-checkbox>": {
|
||||
"@label <label>": "string",
|
||||
"@checked": "boolean",
|
||||
"<checked>": "boolean"
|
||||
},
|
||||
"<shorthand-overlay>": {
|
||||
"nested-tags": {
|
||||
"body": {
|
||||
"@condensed": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": {
|
||||
"shorthand-button": {
|
||||
"@label": "string"
|
||||
},
|
||||
"shorthand-tabs": {
|
||||
"@tabs <tab>[]": {
|
||||
"@label": "string"
|
||||
},
|
||||
"@orientation": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
21
test/fixtures/taglib/test-nested-tags-overlay/marko-tag.json
vendored
Normal file
21
test/fixtures/taglib/test-nested-tags-overlay/marko-tag.json
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
{
|
||||
"renderer": "./renderer",
|
||||
"@header <header>": {
|
||||
"@class": {
|
||||
"type": "string",
|
||||
"target-property": "className"
|
||||
}
|
||||
},
|
||||
"@body <body>": {
|
||||
"@class": {
|
||||
"type": "string",
|
||||
"target-property": "className"
|
||||
}
|
||||
},
|
||||
"@footer <footer>": {
|
||||
"@class": {
|
||||
"type": "string",
|
||||
"target-property": "className"
|
||||
}
|
||||
}
|
||||
}
|
||||
15
test/fixtures/taglib/test-nested-tags-overlay/renderer.js
vendored
Normal file
15
test/fixtures/taglib/test-nested-tags-overlay/renderer.js
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
var marko = require('../../../../');
|
||||
var template = marko.load(require.resolve('./template.marko'));
|
||||
|
||||
exports.renderer = function(input, out) {
|
||||
var header = input.header;
|
||||
var body = input.body;
|
||||
var footer = input.footer;
|
||||
|
||||
template.render({
|
||||
header: header,
|
||||
body: body,
|
||||
footer: footer
|
||||
}, out);
|
||||
|
||||
};
|
||||
13
test/fixtures/taglib/test-nested-tags-overlay/template.marko
vendored
Normal file
13
test/fixtures/taglib/test-nested-tags-overlay/template.marko
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
<div class="overlay">
|
||||
<div class="overlay-header ${data.header.className}" if="data.header">
|
||||
<invoke function="data.header.renderBody(out)"/>
|
||||
</div>
|
||||
|
||||
<div class="overlay-body ${data.body.className}" if="data.body">
|
||||
<invoke function="data.body.renderBody(out)"/>
|
||||
</div>
|
||||
|
||||
<div class="overlay-footer ${data.footer.className}" if="data.footer">
|
||||
<invoke function="data.footer.renderBody(out)"/>
|
||||
</div>
|
||||
</div>
|
||||
7
test/fixtures/taglib/test-nested-tags-tabs/marko-tag.json
vendored
Normal file
7
test/fixtures/taglib/test-nested-tags-tabs/marko-tag.json
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"renderer": "./renderer",
|
||||
"@orientation": "string",
|
||||
"@tabs <tab>[]": {
|
||||
"@title": "string"
|
||||
}
|
||||
}
|
||||
11
test/fixtures/taglib/test-nested-tags-tabs/renderer.js
vendored
Normal file
11
test/fixtures/taglib/test-nested-tags-tabs/renderer.js
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
var marko = require('../../../../');
|
||||
var template = marko.load(require.resolve('./template.marko'));
|
||||
|
||||
exports.renderer = function(input, out) {
|
||||
var tabs = input.tabs;
|
||||
|
||||
template.render({
|
||||
tabs: tabs
|
||||
}, out);
|
||||
|
||||
};
|
||||
14
test/fixtures/taglib/test-nested-tags-tabs/template.marko
vendored
Normal file
14
test/fixtures/taglib/test-nested-tags-tabs/template.marko
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
<var name="tabs" value="data.tabs"/>
|
||||
|
||||
<div class="tabs">
|
||||
<ul>
|
||||
<li for="tab in tabs">
|
||||
${tab.title}
|
||||
</li>
|
||||
</ul>
|
||||
<div class="tab-content">
|
||||
<div class="tab" for="tab in tabs">
|
||||
<invoke function="tab.renderBody(out)"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
1
test/fixtures/templates/nested-tags-repeated/expected.html
vendored
Normal file
1
test/fixtures/templates/nested-tags-repeated/expected.html
vendored
Normal file
@ -0,0 +1 @@
|
||||
<div class="tabs"><ul><li>Tab 1</li><li>Tab 3</li></ul><div class="tab-content"><div class="tab">Tab 1 content</div><div class="tab">Tab 3 content</div></div></div>
|
||||
14
test/fixtures/templates/nested-tags-repeated/template.marko
vendored
Normal file
14
test/fixtures/templates/nested-tags-repeated/template.marko
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
<test-nested-tags-tabs>
|
||||
<test-nested-tags-tabs.tab title="Tab 1">
|
||||
|
||||
Tab 1 content
|
||||
</test-nested-tags-tabs.tab>
|
||||
|
||||
<test-nested-tags-tabs.tab title="Tab 2" if="false">
|
||||
Tab 2 content
|
||||
</test-nested-tags-tabs.tab>
|
||||
|
||||
<test-nested-tags-tabs.tab title="Tab 3">
|
||||
Tab 3 content
|
||||
</test-nested-tags-tabs.tab>
|
||||
</test-nested-tags-tabs>
|
||||
1
test/fixtures/templates/nested-tags-repeated/test.js
vendored
Normal file
1
test/fixtures/templates/nested-tags-repeated/test.js
vendored
Normal file
@ -0,0 +1 @@
|
||||
exports.templateData = {};
|
||||
1
test/fixtures/templates/nested-tags/expected.html
vendored
Normal file
1
test/fixtures/templates/nested-tags/expected.html
vendored
Normal file
@ -0,0 +1 @@
|
||||
<div class="overlay"><div class="overlay-header ">Header content!</div><div class="overlay-body my-body">Body content</div><div class="overlay-footer my-footer">Footer content</div></div>
|
||||
10
test/fixtures/templates/nested-tags/template.marko
vendored
Normal file
10
test/fixtures/templates/nested-tags/template.marko
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
<test-nested-tags-overlay header="data.header">
|
||||
|
||||
<test-nested-tags-overlay.body class="my-body">
|
||||
Body content
|
||||
</test-nested-tags-overlay.body>
|
||||
|
||||
<test-nested-tags-overlay.footer class="my-footer">
|
||||
Footer content
|
||||
</test-nested-tags-overlay.footer>
|
||||
</test-nested-tags-overlay>
|
||||
7
test/fixtures/templates/nested-tags/test.js
vendored
Normal file
7
test/fixtures/templates/nested-tags/test.js
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
exports.templateData = {
|
||||
header: {
|
||||
renderBody: function(out) {
|
||||
out.write('Header content!');
|
||||
}
|
||||
}
|
||||
};
|
||||
43
test/taglib-loader-test.js
Normal file
43
test/taglib-loader-test.js
Normal file
@ -0,0 +1,43 @@
|
||||
'use strict';
|
||||
var chai = require('chai');
|
||||
chai.Assertion.includeStack = true;
|
||||
require('chai').should();
|
||||
var expect = require('chai').expect;
|
||||
var nodePath = require('path');
|
||||
|
||||
describe('taglib-loader' , function() {
|
||||
|
||||
beforeEach(function(done) {
|
||||
for (var k in require.cache) {
|
||||
if (require.cache.hasOwnProperty(k)) {
|
||||
delete require.cache[k];
|
||||
}
|
||||
}
|
||||
|
||||
done();
|
||||
});
|
||||
|
||||
it('should load a taglib with shorthand attributes and tags', function() {
|
||||
var taglibLoader = require('../compiler/taglibs').loader;
|
||||
var taglib = taglibLoader.load(nodePath.join(__dirname, 'fixtures/taglib-shorthand/marko-taglib.json'));
|
||||
expect(taglib != null).to.equal(true);
|
||||
|
||||
var shorthandCheckbox = taglib.tags['shorthand-checkbox'];
|
||||
expect(shorthandCheckbox.attributes.checked.type).to.equal('boolean');
|
||||
expect(shorthandCheckbox.attributes.label.type).to.equal('string');
|
||||
expect(shorthandCheckbox.nestedTags.label.type).to.equal('string');
|
||||
expect(shorthandCheckbox.nestedTags.checked.type).to.equal('boolean');
|
||||
|
||||
var shorthandTabsTag = taglib.tags['shorthand-tabs'];
|
||||
expect(shorthandTabsTag.attributes.orientation != null).to.equal(true);
|
||||
expect(shorthandTabsTag.attributes.orientation.type).to.equal('string');
|
||||
expect(shorthandTabsTag.attributes.tabs.type).to.equal('expression');
|
||||
|
||||
var nestedTabTag = shorthandTabsTag.nestedTags.tab;
|
||||
expect(nestedTabTag.attributes.label != null).to.equal(true);
|
||||
expect(nestedTabTag.isRepeated).to.equal(true);
|
||||
expect(nestedTabTag.targetProperty).to.equal('tabs');
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
Loading…
x
Reference in New Issue
Block a user