mirror of
https://github.com/marko-js/marko.git
synced 2025-12-08 19:26:05 +00:00
Fixes #170 - macro support in Marko v3
This commit is contained in:
parent
70d47e6d5b
commit
c51362e793
@ -30,6 +30,8 @@ var UpdateExpression = require('./ast/UpdateExpression');
|
||||
var UnaryExpression = require('./ast/UnaryExpression');
|
||||
var MemberExpression = require('./ast/MemberExpression');
|
||||
var Code = require('./ast/Code');
|
||||
var InvokeMacro = require('./ast/InvokeMacro');
|
||||
var Macro = require('./ast/Macro');
|
||||
|
||||
var parseExpression = require('./util/parseExpression');
|
||||
|
||||
@ -176,10 +178,22 @@ class Builder {
|
||||
return new If({test, body, else: elseStatement});
|
||||
}
|
||||
|
||||
invokeMacro(name, args, body) {
|
||||
return new InvokeMacro({name, args, body});
|
||||
}
|
||||
|
||||
invokeMacroFromEl(el) {
|
||||
return new InvokeMacro({el});
|
||||
}
|
||||
|
||||
literal(value) {
|
||||
return new Literal({value});
|
||||
}
|
||||
|
||||
macro(name, params, body) {
|
||||
return new Macro({name, params, body});
|
||||
}
|
||||
|
||||
memberExpression(object, property, computed) {
|
||||
object = makeNode(object);
|
||||
property = makeNode(property);
|
||||
@ -212,6 +226,12 @@ class Builder {
|
||||
return new Program({body});
|
||||
}
|
||||
|
||||
renderBodyFunction(body) {
|
||||
let name = 'renderBody';
|
||||
let params = [new Identifier({name: 'out'})];
|
||||
return new FunctionDeclaration({name, params, body});
|
||||
}
|
||||
|
||||
require(path) {
|
||||
path = makeNode(path);
|
||||
|
||||
|
||||
@ -532,7 +532,7 @@ class Generator {
|
||||
errorInfo.node = node;
|
||||
this.context.addError(errorInfo);
|
||||
} else {
|
||||
this.context.addError({node, code, message});
|
||||
this.context.addError({node, message, code});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -10,6 +10,7 @@ var PosInfo = require('./util/PosInfo');
|
||||
var CompileError = require('./CompileError');
|
||||
var path = require('path');
|
||||
var Node = require('./ast/Node');
|
||||
var macros = require('./util/macros');
|
||||
|
||||
function getTaglibPath(taglibPath) {
|
||||
if (typeof window === 'undefined') {
|
||||
@ -38,6 +39,7 @@ class CompileContext {
|
||||
this._srcCharProps = null;
|
||||
this._flags = {};
|
||||
this._errors = [];
|
||||
this._macros = null;
|
||||
}
|
||||
|
||||
getPosInfo(pos) {
|
||||
@ -60,6 +62,23 @@ class CompileContext {
|
||||
}
|
||||
|
||||
addError(errorInfo) {
|
||||
if (errorInfo instanceof Node) {
|
||||
let node = arguments[0];
|
||||
let message = arguments[1];
|
||||
let code = arguments[2];
|
||||
errorInfo = {
|
||||
node,
|
||||
message,
|
||||
code
|
||||
};
|
||||
} else if (typeof errorInfo === 'string') {
|
||||
let message = arguments[0];
|
||||
let code = arguments[1];
|
||||
errorInfo = {
|
||||
message,
|
||||
code
|
||||
};
|
||||
}
|
||||
this._errors.push(new CompileError(errorInfo, this));
|
||||
}
|
||||
|
||||
@ -203,6 +222,30 @@ class CompileContext {
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
isMacro(name) {
|
||||
if (!this._macros) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return this._macros.isMacro(name);
|
||||
}
|
||||
|
||||
getRegisteredMacro(name) {
|
||||
if (!this._macros) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return this._macros.getRegisteredMacro(name);
|
||||
}
|
||||
|
||||
registerMacro(name, params) {
|
||||
if (!this._macros) {
|
||||
this._macros = macros.createMacrosContext();
|
||||
}
|
||||
|
||||
return this._macros.registerMacro(name, params);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = CompileContext;
|
||||
@ -17,12 +17,12 @@ class StartTag extends Node {
|
||||
}
|
||||
|
||||
generateCode(codegen) {
|
||||
var builder = codegen.builder;
|
||||
|
||||
var tagName = this.tagName;
|
||||
var selfClosing = this.selfClosing;
|
||||
var dynamicAttributes = this.dynamicAttributes;
|
||||
|
||||
var builder = codegen.builder;
|
||||
|
||||
// Starting tag
|
||||
codegen.addWriteLiteral('<');
|
||||
|
||||
@ -94,22 +94,9 @@ class EndTag extends Node {
|
||||
class HtmlElement extends Node {
|
||||
constructor(def) {
|
||||
super('HtmlElement');
|
||||
|
||||
var tagName = def.tagName;
|
||||
|
||||
this.tagName = null;
|
||||
this.dynamicTagName = null;
|
||||
|
||||
if (tagName instanceof Node) {
|
||||
if (tagName instanceof Literal) {
|
||||
this.tagName = tagName.value;
|
||||
} else {
|
||||
this.dynamicTagName = tagName;
|
||||
}
|
||||
} else if (typeof tagName === 'string'){
|
||||
this.tagName = tagName;
|
||||
}
|
||||
|
||||
this.tagNameExpression = null;
|
||||
this.setTagName(def.tagName);
|
||||
this._attributes = def.attributes;
|
||||
|
||||
if (!(this._attributes instanceof HtmlAttributeCollection)) {
|
||||
@ -130,7 +117,16 @@ class HtmlElement extends Node {
|
||||
if (tagName) {
|
||||
tagName = codegen.builder.literal(tagName);
|
||||
} else {
|
||||
tagName = this.dynamicTagName;
|
||||
tagName = this.tagNameExpression;
|
||||
}
|
||||
|
||||
var context = codegen.context;
|
||||
|
||||
if (context.isMacro(this.tagName)) {
|
||||
// At code generation time, if this tag corresponds to a registered macro
|
||||
// then invoke the macro based on this HTML element instead of generating
|
||||
// the code to render an HTML element.
|
||||
return codegen.builder.invokeMacroFromEl(this);
|
||||
}
|
||||
|
||||
var attributes = this._attributes && this._attributes.all;
|
||||
@ -247,14 +243,20 @@ class HtmlElement extends Node {
|
||||
}
|
||||
}
|
||||
|
||||
setTagName(newTagName) {
|
||||
this.tagName = newTagName;
|
||||
this.dynamicTagName = null;
|
||||
}
|
||||
|
||||
getDynamicTagName(dynamicTagName) {
|
||||
setTagName(tagName) {
|
||||
this.tagName = null;
|
||||
this.dynamicTagName = dynamicTagName;
|
||||
this.tagNameExpression = null;
|
||||
|
||||
if (tagName instanceof Node) {
|
||||
if (tagName instanceof Literal) {
|
||||
this.tagName = tagName.value;
|
||||
this.tagNameExpression = tagName;
|
||||
} else {
|
||||
this.tagNameExpression = tagName;
|
||||
}
|
||||
} else if (typeof tagName === 'string'){
|
||||
this.tagName = tagName;
|
||||
}
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
|
||||
115
compiler/ast/InvokeMacro.js
Normal file
115
compiler/ast/InvokeMacro.js
Normal file
@ -0,0 +1,115 @@
|
||||
'use strict';
|
||||
|
||||
var Node = require('./Node');
|
||||
var ok = require('assert').ok;
|
||||
var splitJavaScriptArgs = require('../util/splitJavaScriptArgs');
|
||||
|
||||
function removeTrailingUndefineds(args) {
|
||||
var i;
|
||||
var last = args.length-1;
|
||||
|
||||
for (i=last; i>=0; i--) {
|
||||
if (args[i].type !== 'Literal' || args[i].value !== undefined) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (i !== last) {
|
||||
args = args.slice(0, i+1);
|
||||
}
|
||||
|
||||
return args;
|
||||
}
|
||||
|
||||
|
||||
class InvokeMacro extends Node {
|
||||
constructor(def) {
|
||||
super('InvokeMacro');
|
||||
this.el = def.el;
|
||||
this.name = def.name;
|
||||
this.args = def.args;
|
||||
this.body = this.makeContainer(def.body);
|
||||
|
||||
if (this.name != null) {
|
||||
ok(typeof this.name === 'string', 'Invalid macro name: ' + this.name);
|
||||
}
|
||||
}
|
||||
|
||||
generateCode(codegen) {
|
||||
var el = this.el;
|
||||
var name = this.name;
|
||||
var args = this.args;
|
||||
var body = this.body;
|
||||
|
||||
var builder = codegen.builder;
|
||||
|
||||
var macroDef;
|
||||
|
||||
if (el) {
|
||||
name = el.tagName;
|
||||
body = el.body;
|
||||
|
||||
if (typeof name !== 'string') {
|
||||
codegen.context.addError(el, 'Element node with a dynamic tag name cannot be used to invoke a macro', 'ERR_INVOKE_MACRO');
|
||||
return;
|
||||
}
|
||||
|
||||
macroDef = codegen.context.getRegisteredMacro(name);
|
||||
|
||||
if (!macroDef) {
|
||||
codegen.context.addError(el, 'Element node does not correspond to a macro', 'ERR_INVOKE_MACRO');
|
||||
return;
|
||||
}
|
||||
|
||||
if (el.argument) {
|
||||
args = splitJavaScriptArgs(el.argument);
|
||||
} else {
|
||||
args = new Array(macroDef.params.length);
|
||||
for (let i=0; i<args.length; i++) {
|
||||
args[i] = builder.literal(undefined);
|
||||
}
|
||||
|
||||
el.forEachAttribute((attr) => {
|
||||
var paramName = attr.name;
|
||||
var paramIndex = macroDef.getParamIndex(paramName);
|
||||
if (paramIndex == null) {
|
||||
codegen.context.addError(el, 'The "' + name + '" macro does not have a parameter named "' + paramName + '"', 'ERR_INVOKE_MACRO');
|
||||
return;
|
||||
}
|
||||
|
||||
var value = attr.value;
|
||||
if (value == null) {
|
||||
value = builder.literal(true);
|
||||
}
|
||||
args[paramIndex] = value;
|
||||
});
|
||||
}
|
||||
} else {
|
||||
macroDef = codegen.context.getRegisteredMacro(name);
|
||||
if (!macroDef) {
|
||||
codegen.addError('Macro not found with name "' + name + '"', 'ERR_INVOKE_MACRO');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!args) {
|
||||
args = [];
|
||||
}
|
||||
|
||||
while (args.length < macroDef.params.length) {
|
||||
args.push(builder.literal(undefined));
|
||||
}
|
||||
|
||||
if (body && body.length) {
|
||||
args[macroDef.getParamIndex('renderBody')] = builder.renderBodyFunction(body);
|
||||
}
|
||||
|
||||
args[macroDef.getParamIndex('out')] = builder.identifier('out');
|
||||
|
||||
args = removeTrailingUndefineds(args);
|
||||
|
||||
return builder.functionCall(builder.identifier(macroDef.functionName), args);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = InvokeMacro;
|
||||
34
compiler/ast/Macro.js
Normal file
34
compiler/ast/Macro.js
Normal file
@ -0,0 +1,34 @@
|
||||
'use strict';
|
||||
|
||||
var Node = require('./Node');
|
||||
var ok = require('assert').ok;
|
||||
|
||||
class Macro extends Node {
|
||||
constructor(def) {
|
||||
super('Macro');
|
||||
this.name = def.name;
|
||||
this.params = def.params;
|
||||
this.body = this.makeContainer(def.body);
|
||||
|
||||
if (this.params == null) {
|
||||
this.params = [];
|
||||
} else {
|
||||
ok(Array.isArray(this.params), '"params" should be an array');
|
||||
}
|
||||
}
|
||||
|
||||
generateCode(codegen) {
|
||||
var name = this.name;
|
||||
var params = this.params || [];
|
||||
|
||||
var body = this.body;
|
||||
|
||||
var builder = codegen.builder;
|
||||
|
||||
var macroDef = codegen.context.registerMacro(name, params);
|
||||
var functionName = macroDef.functionName;
|
||||
return builder.functionDeclaration(functionName, macroDef.params, body);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Macro;
|
||||
@ -1,22 +1,6 @@
|
||||
'use strict';
|
||||
|
||||
function safeVarName(varName) {
|
||||
|
||||
var parts = varName.split(/[\\/]/);
|
||||
if (parts.length >= 2) {
|
||||
// The varname looks like it was based on a path.
|
||||
// Let's just use the last two parts
|
||||
varName = parts.slice(-2).join('_');
|
||||
}
|
||||
|
||||
return varName.replace(/[^A-Za-z0-9_]/g, '_').replace(/^[0-9]+/, function(match) {
|
||||
var str = '';
|
||||
for (var i=0; i<match.length; i++) {
|
||||
str += '_';
|
||||
}
|
||||
return str;
|
||||
});
|
||||
}
|
||||
var safeVarName = require('./safeVarName');
|
||||
|
||||
class UniqueVars {
|
||||
constructor() {
|
||||
|
||||
88
compiler/util/macros.js
Normal file
88
compiler/util/macros.js
Normal file
@ -0,0 +1,88 @@
|
||||
'use strict';
|
||||
|
||||
var safeVarName = require('./safeVarName');
|
||||
var ok = require('assert').ok;
|
||||
|
||||
class MacrosContext {
|
||||
constructor() {
|
||||
this._byName = {};
|
||||
}
|
||||
|
||||
isMacro(name) {
|
||||
if (!name) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (name.type === 'Literal') {
|
||||
name = name.value;
|
||||
}
|
||||
|
||||
return this._byName.hasOwnProperty(name);
|
||||
}
|
||||
|
||||
getRegisteredMacro(name) {
|
||||
return this._byName[name];
|
||||
}
|
||||
|
||||
registerMacro(name, params) {
|
||||
ok(name, '"name" is required');
|
||||
ok(typeof name === 'string', '"name" should be a string');
|
||||
if (params == null) {
|
||||
params = [];
|
||||
|
||||
} else {
|
||||
ok(Array.isArray(params), '"params" should be an array');
|
||||
}
|
||||
|
||||
|
||||
var hasOut = false;
|
||||
var hasRenderBody = false;
|
||||
params.forEach((param) => {
|
||||
if (param === 'out') {
|
||||
hasOut = true;
|
||||
} else if (param === 'renderBody') {
|
||||
hasRenderBody = true;
|
||||
}
|
||||
});
|
||||
|
||||
if (!hasOut) {
|
||||
params.push('out');
|
||||
}
|
||||
|
||||
if (!hasRenderBody) {
|
||||
params.push('renderBody');
|
||||
}
|
||||
|
||||
var paramIndexes = {};
|
||||
params.forEach((param, i) => {
|
||||
paramIndexes[param] = i;
|
||||
|
||||
if (param === 'out') {
|
||||
hasOut = true;
|
||||
} else if (param === 'renderBody') {
|
||||
hasRenderBody = true;
|
||||
}
|
||||
});
|
||||
|
||||
var functionName = 'macro_' + safeVarName(name);
|
||||
|
||||
var macroDef = {
|
||||
name: name,
|
||||
params: params,
|
||||
functionName: functionName,
|
||||
getParamIndex: function(param) {
|
||||
return paramIndexes[param];
|
||||
}
|
||||
};
|
||||
|
||||
this._byName[name] = macroDef;
|
||||
|
||||
return macroDef;
|
||||
}
|
||||
}
|
||||
|
||||
function createMacrosContext() {
|
||||
return new MacrosContext();
|
||||
}
|
||||
|
||||
exports.createMacrosContext = createMacrosContext;
|
||||
18
compiler/util/safeVarName.js
Normal file
18
compiler/util/safeVarName.js
Normal file
@ -0,0 +1,18 @@
|
||||
function safeVarName(varName) {
|
||||
var parts = varName.split(/[\\/]/);
|
||||
if (parts.length >= 2) {
|
||||
// The varname looks like it was based on a path.
|
||||
// Let's just use the last two parts
|
||||
varName = parts.slice(-2).join('_');
|
||||
}
|
||||
|
||||
return varName.replace(/[^A-Za-z0-9_]/g, '_').replace(/^[0-9]+/, function(match) {
|
||||
var str = '';
|
||||
for (var i=0; i<match.length; i++) {
|
||||
str += '_';
|
||||
}
|
||||
return str;
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = safeVarName;
|
||||
59
compiler/util/splitJavaScriptArgs.js
Normal file
59
compiler/util/splitJavaScriptArgs.js
Normal file
@ -0,0 +1,59 @@
|
||||
'use strict';
|
||||
var removeComments = require('./removeComments');
|
||||
var parseExpression = require('./parseExpression');
|
||||
var tokenizer = require('./tokenizer').create([
|
||||
{
|
||||
name: 'stringDouble',
|
||||
pattern: /"(?:[^"]|\\")*"/,
|
||||
},
|
||||
{
|
||||
name: 'stringSingle',
|
||||
pattern: /'(?:[^']|\\')*'/
|
||||
},
|
||||
{
|
||||
name: 'groupOpen',
|
||||
pattern: /[\{\(\[]/
|
||||
},
|
||||
{
|
||||
name: 'groupClose',
|
||||
pattern: /[\}\)\]]/
|
||||
},
|
||||
{
|
||||
name: 'comma',
|
||||
pattern: /[,]/
|
||||
}
|
||||
]);
|
||||
|
||||
module.exports = function(str) {
|
||||
str = removeComments(str);
|
||||
|
||||
let depth = 0;
|
||||
var argStart = 0;
|
||||
var args = [];
|
||||
|
||||
function finishPrevArg(end) {
|
||||
var arg = str.substring(argStart, end);
|
||||
args.push(parseExpression(arg));
|
||||
}
|
||||
|
||||
tokenizer.forEachToken(str, (token) => {
|
||||
switch(token.name) {
|
||||
case 'groupOpen':
|
||||
depth++;
|
||||
break;
|
||||
case 'groupClose':
|
||||
depth--;
|
||||
break;
|
||||
case 'comma':
|
||||
if (depth === 0) {
|
||||
finishPrevArg(token.start);
|
||||
argStart = token.end;
|
||||
}
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
finishPrevArg(str.length);
|
||||
|
||||
return args;
|
||||
};
|
||||
@ -422,9 +422,41 @@ if (true) {
|
||||
}
|
||||
```
|
||||
|
||||
### invokeMacro(name, args, body)
|
||||
|
||||
Returns a node to generate the code to invoke a macro with the given name, args and body
|
||||
|
||||
For example:
|
||||
|
||||
```javascript
|
||||
builder.invokeMacro(
|
||||
'greeting',
|
||||
[
|
||||
builder.literal('Frank'),
|
||||
builder.literal(10),
|
||||
],
|
||||
[
|
||||
builder.text(builder.literal('This is the body passed to the macro'))
|
||||
])
|
||||
```
|
||||
|
||||
### invokeMacroFromEl(el)
|
||||
|
||||
Returns a node to generate the code to invoke a macro based on the provided `HtmlElement` node.
|
||||
|
||||
For example:
|
||||
|
||||
```javascript
|
||||
var el = builder.htmlElement('greeting', {
|
||||
name: builder.literal('Frank'),
|
||||
age: builder.literal(10)
|
||||
});
|
||||
builder.invokeMacroFromEl(el)
|
||||
```
|
||||
|
||||
### literal(value)
|
||||
|
||||
Returns code to generate a JavaScript code for literal strings, numbers, booleans, objects and arrays.
|
||||
Returns a node to generate a JavaScript code for literal strings, numbers, booleans, objects and arrays.
|
||||
|
||||
|
||||
For example:
|
||||
@ -467,6 +499,10 @@ var aString = "abc",
|
||||
]
|
||||
```
|
||||
|
||||
### macro(name, params, body)
|
||||
|
||||
Returns a node that generates a macro function with the given name, params and body content. The `InvokeMacro` node should be used to generate the code to invoke the macro.
|
||||
|
||||
### negate(argument)
|
||||
|
||||
Returns a node that generates the following code:
|
||||
|
||||
7
taglibs/core/macro-body-tag.js
Normal file
7
taglibs/core/macro-body-tag.js
Normal file
@ -0,0 +1,7 @@
|
||||
module.exports = function codeGenerator(elNode, codegen) {
|
||||
var builder = codegen.builder;
|
||||
|
||||
return builder.ifStatement(builder.identifier('renderBody'), [
|
||||
builder.functionCall('renderBody', ['out'])
|
||||
]);
|
||||
};
|
||||
26
taglibs/core/macro-tag.js
Normal file
26
taglibs/core/macro-tag.js
Normal file
@ -0,0 +1,26 @@
|
||||
module.exports = function codeGenerator(elNode, codegen) {
|
||||
|
||||
var attributes = elNode.attributes;
|
||||
if (!attributes.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
var defAttr = attributes[0];
|
||||
if (!defAttr.argument) {
|
||||
return;
|
||||
}
|
||||
|
||||
var body = elNode.body;
|
||||
var macroName = defAttr.name;
|
||||
var argument = defAttr.argument;
|
||||
var params;
|
||||
if (argument) {
|
||||
params = argument.split(/\s*,\s*/);
|
||||
} else {
|
||||
params = [];
|
||||
}
|
||||
|
||||
var builder = codegen.builder;
|
||||
|
||||
return builder.macro(macroName, params, body);
|
||||
};
|
||||
@ -11,6 +11,12 @@
|
||||
"<if>": {
|
||||
"node-factory": "./if-tag"
|
||||
},
|
||||
"<macro>": {
|
||||
"code-generator": "./macro-tag"
|
||||
},
|
||||
"<macro-body>": {
|
||||
"code-generator": "./macro-body-tag"
|
||||
},
|
||||
"<template-init>": {
|
||||
"code-generator": "./template-init-tag",
|
||||
"body": "static-text"
|
||||
|
||||
@ -1,146 +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.
|
||||
*/
|
||||
/**
|
||||
* Utility class to support sub-attributes in an XML attribute. Each sub-attribute must
|
||||
* be separated by a semicolon. Within each sub-attribute, the name/value pair must
|
||||
* be split using an equal sign. However, the name for the first sub-attribute
|
||||
* is optional and a default name can be provided when reading the sub-attributes.
|
||||
*
|
||||
* <p>
|
||||
* Sub-attribute format:
|
||||
* (<attr-value>)?(<attr-name>=<attr-value>;)*(<attr-name>=<attr-value>)
|
||||
*
|
||||
*
|
||||
*
|
||||
*/
|
||||
'use strict';
|
||||
var regExp = /"(?:[^"]|\\")*"|'(?:[^']|\\')*'|==|===|[;=]/g;
|
||||
|
||||
/**
|
||||
* Parses the provided string to find the sub-attributes that it contains.
|
||||
* The parsed output can be either returned as an array or a map. By default,
|
||||
* the parsed output is returned as a map where each property corresponds
|
||||
* to a sub-attribute. However, if the order of the sub-attributes is important
|
||||
* then the "ordered" option can be set to "true" and
|
||||
* an array will instead be returned where each element in the array is an object
|
||||
* with a name and value property that corresponds to the matching sub-attribute.
|
||||
*
|
||||
* <p>
|
||||
* Supported options:
|
||||
* <ul>
|
||||
* <li>ordered (boolean, defaults to "false") - If true then an array is returned (see above). Otherwise, an object is returned.
|
||||
* </ul>
|
||||
*
|
||||
* @memberOf raptor/templating/compiler$AttributeSplitter
|
||||
* @param attr {String} The attribute to split
|
||||
* @param types {Object} Type definitions for the possible sub-attributes.
|
||||
* @param options
|
||||
* @returns
|
||||
*/
|
||||
module.exports = function (attr, types, options) {
|
||||
if (!options) {
|
||||
options = {};
|
||||
}
|
||||
var partStart = 0;
|
||||
var ordered = options.ordered === true;
|
||||
var defaultName = options.defaultName;
|
||||
var removeDashes = options.removeDashes === true;
|
||||
var matches;
|
||||
var equalIndex = -1;
|
||||
var result = ordered ? [] : {};
|
||||
function handleError(message) {
|
||||
if (options.errorHandler) {
|
||||
options.errorHandler(message);
|
||||
return;
|
||||
} else {
|
||||
throw new Error(message);
|
||||
}
|
||||
}
|
||||
function finishPart(endIndex) {
|
||||
if (partStart === endIndex) {
|
||||
//The part is an empty string... ignore it
|
||||
return;
|
||||
}
|
||||
var name;
|
||||
var value;
|
||||
if (equalIndex != -1) {
|
||||
name = attr.substring(partStart, equalIndex).trim();
|
||||
value = attr.substring(equalIndex + 1, endIndex).trim();
|
||||
} else {
|
||||
if (defaultName) {
|
||||
name = defaultName;
|
||||
value = attr.substring(partStart, endIndex).trim();
|
||||
if (!value.length) {
|
||||
return; //ignore empty parts
|
||||
}
|
||||
} else {
|
||||
name = attr.substring(partStart, endIndex).trim();
|
||||
}
|
||||
}
|
||||
|
||||
if (!name.length && !value.length) {
|
||||
equalIndex = -1;
|
||||
return; //ignore empty parts
|
||||
}
|
||||
|
||||
if (types) {
|
||||
var type = types[name] || types['*'];
|
||||
if (type) {
|
||||
if (type.name) {
|
||||
name = type.name;
|
||||
}
|
||||
} else {
|
||||
return handleError('Invalid sub-attribute name of "' + name + '"');
|
||||
}
|
||||
}
|
||||
if (name && removeDashes) {
|
||||
name = name.replace(/-([a-z])/g, function (match, lower) {
|
||||
return lower.toUpperCase();
|
||||
});
|
||||
}
|
||||
if (ordered) {
|
||||
result.push({
|
||||
name: name,
|
||||
value: value
|
||||
});
|
||||
} else {
|
||||
result[name] = value;
|
||||
}
|
||||
equalIndex = -1; //Reset the equal index
|
||||
}
|
||||
/*
|
||||
* Keep searching the string for the relevant tokens.
|
||||
*
|
||||
* NOTE: The regular expression will also return matches for JavaScript strings,
|
||||
* but they are just ignored. This ensures that semicolons inside strings
|
||||
* are not treated as
|
||||
*/
|
||||
while ((matches = regExp.exec(attr))) {
|
||||
//console.error(matches[0]);
|
||||
if (matches[0] == ';') {
|
||||
finishPart(matches.index);
|
||||
partStart = matches.index + 1;
|
||||
equalIndex = -1;
|
||||
} else if (matches[0] == '=') {
|
||||
if (equalIndex == -1) {
|
||||
equalIndex = matches.index;
|
||||
}
|
||||
}
|
||||
}
|
||||
finishPart(attr.length);
|
||||
//console.error("AttributeSplitter - result: ", result);
|
||||
return result;
|
||||
};
|
||||
@ -2,12 +2,12 @@
|
||||
var Expression = require('../../../compiler/ast/Expression');
|
||||
var Literal = require('../../../compiler/ast/Literal');
|
||||
var Identifier = require('../../../compiler/ast/Identifier');
|
||||
var removeComments = require('./removeComments');
|
||||
var removeComments = require('../../../compiler/util/removeComments');
|
||||
|
||||
var integerRegExp = /^-?\d+$/;
|
||||
var numberRegExp = /^-?(?:\d+|\d+\.\d*|\d*\.\d+|\d+\.\d+)$/;
|
||||
|
||||
var tokenizer = require('./tokenizer').create([
|
||||
var tokenizer = require('../../../compiler/util/tokenizer').create([
|
||||
{
|
||||
name: 'stringDouble',
|
||||
pattern: /"(?:[^"]|\\")*"/,
|
||||
|
||||
8
test/fixtures/codegen/autotest/invokeMacro-body/expected.js
vendored
Normal file
8
test/fixtures/codegen/autotest/invokeMacro-body/expected.js
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
function macro_greeting(name, age, out, renderBody) {
|
||||
out.w("Hello " +
|
||||
escapeXml(name));
|
||||
}
|
||||
|
||||
macro_greeting("Frank", 10, out, function renderBody(out) {
|
||||
out.w("This is the body passed to the macro");
|
||||
});
|
||||
19
test/fixtures/codegen/autotest/invokeMacro-body/index.js
vendored
Normal file
19
test/fixtures/codegen/autotest/invokeMacro-body/index.js
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = function(builder) {
|
||||
return builder.program([
|
||||
builder.macro('greeting', ['name', 'age'], [
|
||||
builder.text(builder.literal('Hello ')),
|
||||
builder.text(builder.identifier('name'))
|
||||
]),
|
||||
builder.invokeMacro(
|
||||
'greeting',
|
||||
[
|
||||
builder.literal('Frank'),
|
||||
builder.literal(10),
|
||||
],
|
||||
[
|
||||
builder.text(builder.literal('This is the body passed to the macro'))
|
||||
])
|
||||
]);
|
||||
};
|
||||
6
test/fixtures/codegen/autotest/invokeMacro/expected.js
vendored
Normal file
6
test/fixtures/codegen/autotest/invokeMacro/expected.js
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
function macro_greeting(name, age, out, renderBody) {
|
||||
out.w("Hello " +
|
||||
escapeXml(name));
|
||||
}
|
||||
|
||||
macro_greeting("Frank", 10, out);
|
||||
16
test/fixtures/codegen/autotest/invokeMacro/index.js
vendored
Normal file
16
test/fixtures/codegen/autotest/invokeMacro/index.js
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = function(builder) {
|
||||
return builder.program([
|
||||
builder.macro('greeting', ['name', 'age'], [
|
||||
builder.text(builder.literal('Hello ')),
|
||||
builder.text(builder.identifier('name'))
|
||||
]),
|
||||
builder.invokeMacro(
|
||||
'greeting',
|
||||
[
|
||||
builder.literal('Frank'),
|
||||
builder.literal(10),
|
||||
])
|
||||
]);
|
||||
};
|
||||
8
test/fixtures/codegen/autotest/invokeMacroFromEl-body-and-argument/expected.js
vendored
Normal file
8
test/fixtures/codegen/autotest/invokeMacroFromEl-body-and-argument/expected.js
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
function macro_greeting(name, age, out, renderBody) {
|
||||
out.w("Hello " +
|
||||
escapeXml(name));
|
||||
}
|
||||
|
||||
macro_greeting("Frank", 10, out, function renderBody(out) {
|
||||
out.w("This is the body passed to the macro");
|
||||
});
|
||||
17
test/fixtures/codegen/autotest/invokeMacroFromEl-body-and-argument/index.js
vendored
Normal file
17
test/fixtures/codegen/autotest/invokeMacroFromEl-body-and-argument/index.js
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = function(builder) {
|
||||
return builder.program([
|
||||
builder.macro('greeting', ['name', 'age'], [
|
||||
builder.text(builder.literal('Hello ')),
|
||||
builder.text(builder.identifier('name'))
|
||||
]),
|
||||
builder.invokeMacroFromEl(builder.htmlElement(
|
||||
'greeting',
|
||||
[], /* empty attributes */
|
||||
[
|
||||
builder.text(builder.literal('This is the body passed to the macro'))
|
||||
],
|
||||
'"Frank", 10') /* argument string */)
|
||||
]);
|
||||
};
|
||||
6
test/fixtures/codegen/autotest/invokeMacroFromEl/expected.js
vendored
Normal file
6
test/fixtures/codegen/autotest/invokeMacroFromEl/expected.js
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
function macro_greeting(name, age, out, renderBody) {
|
||||
out.w("Hello " +
|
||||
escapeXml(name));
|
||||
}
|
||||
|
||||
macro_greeting("Frank", 10, out);
|
||||
14
test/fixtures/codegen/autotest/invokeMacroFromEl/index.js
vendored
Normal file
14
test/fixtures/codegen/autotest/invokeMacroFromEl/index.js
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = function(builder) {
|
||||
return builder.program([
|
||||
builder.macro('greeting', ['name', 'age'], [
|
||||
builder.text(builder.literal('Hello ')),
|
||||
builder.text(builder.identifier('name'))
|
||||
]),
|
||||
builder.invokeMacroFromEl(builder.htmlElement('greeting', {
|
||||
name: builder.literal('Frank'),
|
||||
age: builder.literal(10)
|
||||
}))
|
||||
]);
|
||||
};
|
||||
4
test/fixtures/codegen/autotest/macro/expected.js
vendored
Normal file
4
test/fixtures/codegen/autotest/macro/expected.js
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
function macro_greeting(name, age, out, renderBody) {
|
||||
out.w("Hello " +
|
||||
escapeXml(name));
|
||||
}
|
||||
8
test/fixtures/codegen/autotest/macro/index.js
vendored
Normal file
8
test/fixtures/codegen/autotest/macro/index.js
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = function(builder) {
|
||||
return builder.macro('greeting', ['name', 'age'], [
|
||||
builder.text(builder.literal('Hello ')),
|
||||
builder.text(builder.identifier('name'))
|
||||
]);
|
||||
};
|
||||
@ -1 +0,0 @@
|
||||
<p class="greeting">Hello, World!</p>, <p class="greeting">Hello, Frank!</p><div class="section"><h1><a href="http://www.ebay.com/">ebay</a></h1><p><i>Visit eBay</i></p></div>
|
||||
@ -1,26 +0,0 @@
|
||||
<def function="greeting(name)">
|
||||
<p class="greeting">Hello, ${name}!</p>
|
||||
</def>
|
||||
|
||||
${greeting("World")},
|
||||
${greeting("Frank")}
|
||||
|
||||
|
||||
<def function="section(url, title, body)" body-param="body">
|
||||
<div class="section">
|
||||
<h1>
|
||||
<a href="$url">
|
||||
$title
|
||||
</a>
|
||||
</h1>
|
||||
<p>
|
||||
${body}
|
||||
</p>
|
||||
</div>
|
||||
</def>
|
||||
|
||||
<invoke function="section" url="http://www.ebay.com/" title="ebay">
|
||||
<i>
|
||||
Visit eBay
|
||||
</i>
|
||||
</invoke>
|
||||
1
test/fixtures/render/autotest/macro-body/expected.html
vendored
Normal file
1
test/fixtures/render/autotest/macro-body/expected.html
vendored
Normal file
@ -0,0 +1 @@
|
||||
<div><h1>My Alert</h1><p>Something went wrong!</p></div>
|
||||
14
test/fixtures/render/autotest/macro-body/template.marko
vendored
Normal file
14
test/fixtures/render/autotest/macro-body/template.marko
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
<macro alert(title)>
|
||||
<div>
|
||||
<h1>
|
||||
${title}
|
||||
</h1>
|
||||
<p>
|
||||
<macro-body/>
|
||||
</p>
|
||||
</div>
|
||||
</macro>
|
||||
|
||||
<alert title="My Alert">
|
||||
Something went wrong!
|
||||
</alert>
|
||||
1
test/fixtures/render/autotest/macro-boolean/expected.html
vendored
Normal file
1
test/fixtures/render/autotest/macro-boolean/expected.html
vendored
Normal file
@ -0,0 +1 @@
|
||||
<div class="null">Hello Frank! You are 30 years old.</div><div class="hidden">Hello John! You are 10 years old.</div><div class="hidden">Hello John! You are 10 years old.</div><div class="null">Hello John! You are 10 years old.</div>
|
||||
10
test/fixtures/render/autotest/macro-boolean/template.marko
vendored
Normal file
10
test/fixtures/render/autotest/macro-boolean/template.marko
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
<macro greeting(name, age, hidden)>
|
||||
<div class=(hidden === true ? 'hidden' : null)>
|
||||
Hello ${name}! You are ${age} years old.
|
||||
</div>
|
||||
</macro>
|
||||
|
||||
<greeting name="Frank" age=30/>
|
||||
<greeting name="John" age=10 hidden/>
|
||||
<greeting name="John" age=10 hidden=true />
|
||||
<greeting name="John" age=10 hidden=false />
|
||||
1
test/fixtures/render/autotest/macro-boolean/test.js
vendored
Normal file
1
test/fixtures/render/autotest/macro-boolean/test.js
vendored
Normal file
@ -0,0 +1 @@
|
||||
exports.templateData = {};
|
||||
1
test/fixtures/render/autotest/macro-positional-args/expected.html
vendored
Normal file
1
test/fixtures/render/autotest/macro-positional-args/expected.html
vendored
Normal file
@ -0,0 +1 @@
|
||||
Hello Frank! You are 30 years old.Hello Doe, John! You are 10 years old.
|
||||
6
test/fixtures/render/autotest/macro-positional-args/template.marko
vendored
Normal file
6
test/fixtures/render/autotest/macro-positional-args/template.marko
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
<macro greeting(name, age)>
|
||||
Hello ${name}! You are ${age} years old.
|
||||
</macro>
|
||||
|
||||
<greeting('Frank', 30)/>
|
||||
<greeting('Doe, John', 10)/>
|
||||
1
test/fixtures/render/autotest/macro-positional-args/test.js
vendored
Normal file
1
test/fixtures/render/autotest/macro-positional-args/test.js
vendored
Normal file
@ -0,0 +1 @@
|
||||
exports.templateData = {};
|
||||
1
test/fixtures/render/autotest/macro/expected.html
vendored
Normal file
1
test/fixtures/render/autotest/macro/expected.html
vendored
Normal file
@ -0,0 +1 @@
|
||||
Hello Frank! You are 30 years old.Hello John! You are 10 years old.
|
||||
6
test/fixtures/render/autotest/macro/template.marko
vendored
Normal file
6
test/fixtures/render/autotest/macro/template.marko
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
<macro greeting(name, age)>
|
||||
Hello ${name}! You are ${age} years old.
|
||||
</macro>
|
||||
|
||||
<greeting name="Frank" age=30/>
|
||||
<greeting name="John" age=10/>
|
||||
1
test/fixtures/render/autotest/macro/test.js
vendored
Normal file
1
test/fixtures/render/autotest/macro/test.js
vendored
Normal file
@ -0,0 +1 @@
|
||||
exports.templateData = {};
|
||||
Loading…
x
Reference in New Issue
Block a user