Introduce true global variables
The basic idea is that if we don't find a variable `foo` then we fallback to retrieving the value from the tiddler `$:/global/foo`, if it exists. This allows us to replace the usual importvariables-based mechanism for global definitions, avoiding cluttering up the variable namespace with every macro. In order to permit subprocedures to be overridden, we also introduce a mechanism for conditional definitions: preceding the word definition|procedure|function|widget with a + causes the definition only to occur if the specified variable doesn't already exist. In the next commit we'll apply this mechanism to the tabs macro
This commit is contained in:
parent
a2fbebf509
commit
f636349007
|
@ -35,7 +35,7 @@ Instantiate parse rule
|
||||||
exports.init = function(parser) {
|
exports.init = function(parser) {
|
||||||
this.parser = parser;
|
this.parser = parser;
|
||||||
// Regexp to match
|
// Regexp to match
|
||||||
this.matchRegExp = /^\\(function|procedure|widget)\s+([^(\s]+)(\(\s*([^)]*)\))?(\s*\r?\n)?/mg;
|
this.matchRegExp = /^\\(\+?)(function|procedure|widget)\s+([^(\s]+)(\(\s*([^)]*)\))?(\s*\r?\n)?/mg;
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -46,12 +46,12 @@ exports.parse = function() {
|
||||||
this.parser.pos = this.matchRegExp.lastIndex;
|
this.parser.pos = this.matchRegExp.lastIndex;
|
||||||
// Parse the parameters
|
// Parse the parameters
|
||||||
var params = [];
|
var params = [];
|
||||||
if(this.match[3]) {
|
if(this.match[4]) {
|
||||||
params = $tw.utils.parseParameterDefinition(this.match[4]);
|
params = $tw.utils.parseParameterDefinition(this.match[5]);
|
||||||
}
|
}
|
||||||
// Is this a multiline definition?
|
// Is this a multiline definition?
|
||||||
var reEnd;
|
var reEnd;
|
||||||
if(this.match[5]) {
|
if(this.match[6]) {
|
||||||
// If so, the end of the body is marked with \end
|
// If so, the end of the body is marked with \end
|
||||||
reEnd = /(\r?\n\\end[^\S\n\r]*(?:$|\r?\n))/mg;
|
reEnd = /(\r?\n\\end[^\S\n\r]*(?:$|\r?\n))/mg;
|
||||||
} else {
|
} else {
|
||||||
|
@ -75,22 +75,25 @@ exports.parse = function() {
|
||||||
var parseTreeNodes = [{
|
var parseTreeNodes = [{
|
||||||
type: "set",
|
type: "set",
|
||||||
attributes: {
|
attributes: {
|
||||||
name: {type: "string", value: this.match[2]},
|
name: {type: "string", value: this.match[3]},
|
||||||
value: {type: "string", value: text}
|
value: {type: "string", value: text}
|
||||||
},
|
},
|
||||||
children: [],
|
children: [],
|
||||||
params: params
|
params: params
|
||||||
}];
|
}];
|
||||||
if(this.match[1] === "function") {
|
if(this.match[2] === "function") {
|
||||||
parseTreeNodes[0].isFunctionDefinition = true;
|
parseTreeNodes[0].isFunctionDefinition = true;
|
||||||
} else if(this.match[1] === "procedure") {
|
} else if(this.match[2] === "procedure") {
|
||||||
parseTreeNodes[0].isProcedureDefinition = true;
|
parseTreeNodes[0].isProcedureDefinition = true;
|
||||||
} else if(this.match[1] === "widget") {
|
} else if(this.match[2] === "widget") {
|
||||||
parseTreeNodes[0].isWidgetDefinition = true;
|
parseTreeNodes[0].isWidgetDefinition = true;
|
||||||
}
|
}
|
||||||
if(this.parser.configTrimWhiteSpace) {
|
if(this.parser.configTrimWhiteSpace) {
|
||||||
parseTreeNodes[0].configTrimWhiteSpace = true;
|
parseTreeNodes[0].configTrimWhiteSpace = true;
|
||||||
}
|
}
|
||||||
|
if(this.match[1] === "+") {
|
||||||
|
$tw.utils.addAttributeToParseTreeNode(parseTreeNodes[0],"conditional","yes");
|
||||||
|
}
|
||||||
return parseTreeNodes;
|
return parseTreeNodes;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,7 @@ Instantiate parse rule
|
||||||
exports.init = function(parser) {
|
exports.init = function(parser) {
|
||||||
this.parser = parser;
|
this.parser = parser;
|
||||||
// Regexp to match
|
// Regexp to match
|
||||||
this.matchRegExp = /^\\define\s+([^(\s]+)\(\s*([^)]*)\)(\s*\r?\n)?/mg;
|
this.matchRegExp = /^\\(\+?)define\s+([^(\s]+)\(\s*([^)]*)\)(\s*\r?\n)?/mg;
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -37,7 +37,7 @@ exports.parse = function() {
|
||||||
// Move past the macro name and parameters
|
// Move past the macro name and parameters
|
||||||
this.parser.pos = this.matchRegExp.lastIndex;
|
this.parser.pos = this.matchRegExp.lastIndex;
|
||||||
// Parse the parameters
|
// Parse the parameters
|
||||||
var paramString = this.match[2],
|
var paramString = this.match[3],
|
||||||
params = [];
|
params = [];
|
||||||
if(paramString !== "") {
|
if(paramString !== "") {
|
||||||
var reParam = /\s*([A-Za-z0-9\-_]+)(?:\s*:\s*(?:"""([\s\S]*?)"""|"([^"]*)"|'([^']*)'|\[\[([^\]]*)\]\]|([^"'\s]+)))?/mg,
|
var reParam = /\s*([A-Za-z0-9\-_]+)(?:\s*:\s*(?:"""([\s\S]*?)"""|"([^"]*)"|'([^']*)'|\[\[([^\]]*)\]\]|([^"'\s]+)))?/mg,
|
||||||
|
@ -56,7 +56,7 @@ exports.parse = function() {
|
||||||
}
|
}
|
||||||
// Is this a multiline definition?
|
// Is this a multiline definition?
|
||||||
var reEnd;
|
var reEnd;
|
||||||
if(this.match[3]) {
|
if(this.match[4]) {
|
||||||
// If so, the end of the body is marked with \end
|
// If so, the end of the body is marked with \end
|
||||||
reEnd = /(\r?\n\\end[^\S\n\r]*(?:$|\r?\n))/mg;
|
reEnd = /(\r?\n\\end[^\S\n\r]*(?:$|\r?\n))/mg;
|
||||||
} else {
|
} else {
|
||||||
|
@ -77,16 +77,22 @@ exports.parse = function() {
|
||||||
text = "";
|
text = "";
|
||||||
}
|
}
|
||||||
// Save the macro definition
|
// Save the macro definition
|
||||||
return [{
|
var parseTreeNodes = [{
|
||||||
type: "set",
|
type: "set",
|
||||||
attributes: {
|
attributes: {
|
||||||
name: {type: "string", value: this.match[1]},
|
name: {type: "string", value: this.match[2]},
|
||||||
value: {type: "string", value: text}
|
value: {type: "string", value: text}
|
||||||
},
|
},
|
||||||
children: [],
|
children: [],
|
||||||
params: params,
|
params: params,
|
||||||
isMacroDefinition: true
|
isMacroDefinition: true
|
||||||
}];
|
}];
|
||||||
|
$tw.utils.addAttributeToParseTreeNode(parseTreeNodes[0],"name",this.match[2]);
|
||||||
|
$tw.utils.addAttributeToParseTreeNode(parseTreeNodes[0],"value",text);
|
||||||
|
if(this.match[1] === "+") {
|
||||||
|
$tw.utils.addAttributeToParseTreeNode(parseTreeNodes[0],"conditional","yes");
|
||||||
|
}
|
||||||
|
return parseTreeNodes;
|
||||||
};
|
};
|
||||||
|
|
||||||
})();
|
})();
|
||||||
|
|
|
@ -47,17 +47,20 @@ SetWidget.prototype.execute = function() {
|
||||||
this.setIndex = this.getAttribute("index");
|
this.setIndex = this.getAttribute("index");
|
||||||
this.setValue = this.getAttribute("value");
|
this.setValue = this.getAttribute("value");
|
||||||
this.setEmptyValue = this.getAttribute("emptyValue");
|
this.setEmptyValue = this.getAttribute("emptyValue");
|
||||||
// Set context variable
|
this.setConditional = this.getAttribute("conditional","no") === "yes";
|
||||||
if(this.parseTreeNode.isMacroDefinition) {
|
// Set context variable, checking for a conditional assignment
|
||||||
this.setVariable(this.setName,this.getValue(),this.parseTreeNode.params,true);
|
if(!this.setConditional || this.getVariableInfo(this.setName).text === undefined) {
|
||||||
} else if(this.parseTreeNode.isFunctionDefinition) {
|
if(this.parseTreeNode.isMacroDefinition) {
|
||||||
this.setVariable(this.setName,this.getValue(),this.parseTreeNode.params,undefined,{isFunctionDefinition: true});
|
this.setVariable(this.setName,this.getValue(),this.parseTreeNode.params,true);
|
||||||
} else if(this.parseTreeNode.isProcedureDefinition) {
|
} else if(this.parseTreeNode.isFunctionDefinition) {
|
||||||
this.setVariable(this.setName,this.getValue(),this.parseTreeNode.params,undefined,{isProcedureDefinition: true, configTrimWhiteSpace: this.parseTreeNode.configTrimWhiteSpace});
|
this.setVariable(this.setName,this.getValue(),this.parseTreeNode.params,undefined,{isFunctionDefinition: true});
|
||||||
} else if(this.parseTreeNode.isWidgetDefinition) {
|
} else if(this.parseTreeNode.isProcedureDefinition) {
|
||||||
this.setVariable(this.setName,this.getValue(),this.parseTreeNode.params,undefined,{isWidgetDefinition: true, configTrimWhiteSpace: this.parseTreeNode.configTrimWhiteSpace});
|
this.setVariable(this.setName,this.getValue(),this.parseTreeNode.params,undefined,{isProcedureDefinition: true, configTrimWhiteSpace: this.parseTreeNode.configTrimWhiteSpace});
|
||||||
} else {
|
} else if(this.parseTreeNode.isWidgetDefinition) {
|
||||||
this.setVariable(this.setName,this.getValue());
|
this.setVariable(this.setName,this.getValue(),this.parseTreeNode.params,undefined,{isWidgetDefinition: true, configTrimWhiteSpace: this.parseTreeNode.configTrimWhiteSpace});
|
||||||
|
} else {
|
||||||
|
this.setVariable(this.setName,this.getValue());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Construct the child widgets
|
// Construct the child widgets
|
||||||
this.makeChildWidgets();
|
this.makeChildWidgets();
|
||||||
|
@ -111,7 +114,7 @@ Selectively refreshes the widget if needed. Returns true if the widget or any of
|
||||||
*/
|
*/
|
||||||
SetWidget.prototype.refresh = function(changedTiddlers) {
|
SetWidget.prototype.refresh = function(changedTiddlers) {
|
||||||
var changedAttributes = this.computeAttributes();
|
var changedAttributes = this.computeAttributes();
|
||||||
if(changedAttributes.name || changedAttributes.filter || changedAttributes.select || changedAttributes.tiddler || (this.setTiddler && changedTiddlers[this.setTiddler]) || changedAttributes.field || changedAttributes.index || changedAttributes.value || changedAttributes.emptyValue ||
|
if(changedAttributes.name || changedAttributes.filter || changedAttributes.select || changedAttributes.tiddler || (this.setTiddler && changedTiddlers[this.setTiddler]) || changedAttributes.field || changedAttributes.index || changedAttributes.value || changedAttributes.emptyValue || changedAttributes.conditional ||
|
||||||
(this.setFilter && this.getValue() != this.variables[this.setName].value)) {
|
(this.setFilter && this.getValue() != this.variables[this.setName].value)) {
|
||||||
this.refreshSelf();
|
this.refreshSelf();
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -116,6 +116,7 @@ options: see below
|
||||||
Options include
|
Options include
|
||||||
params: array of {name:, value:} for each parameter
|
params: array of {name:, value:} for each parameter
|
||||||
defaultValue: default value if the variable is not defined
|
defaultValue: default value if the variable is not defined
|
||||||
|
allowSelfAssigned: if true, includes the current widget in the context chain instead of just the parent
|
||||||
|
|
||||||
Returns an object with the following fields:
|
Returns an object with the following fields:
|
||||||
|
|
||||||
|
@ -124,32 +125,54 @@ text: text of variable, with parameters properly substituted
|
||||||
*/
|
*/
|
||||||
Widget.prototype.getVariableInfo = function(name,options) {
|
Widget.prototype.getVariableInfo = function(name,options) {
|
||||||
options = options || {};
|
options = options || {};
|
||||||
var actualParams = options.params || [],
|
var self = this,
|
||||||
parentWidget = this.parentWidget;
|
actualParams = options.params || [],
|
||||||
// Check for the variable defined in the parent widget (or an ancestor in the prototype chain)
|
currWidget = options.allowSelfAssigned ? this : this.parentWidget,
|
||||||
if(parentWidget && name in parentWidget.variables) {
|
processVariable = function(variable) {
|
||||||
var variable = parentWidget.variables[name],
|
var originalValue = variable.value,
|
||||||
originalValue = variable.value,
|
value = originalValue,
|
||||||
value = originalValue;
|
params = [];
|
||||||
// Only substitute parameter and variable references if this variable was defined with the \define pragma
|
// Only substitute parameter and variable references if this variable was defined with the \define pragma
|
||||||
if(variable.isMacroDefinition) {
|
if(variable.isMacroDefinition) {
|
||||||
var params = this.resolveVariableParameters(variable.params,actualParams);
|
params = self.resolveVariableParameters(variable.params,actualParams);
|
||||||
// Substitute any parameters specified in the definition
|
// Substitute any parameters specified in the definition
|
||||||
$tw.utils.each(params,function(param) {
|
$tw.utils.each(params,function(param) {
|
||||||
value = $tw.utils.replaceString(value,new RegExp("\\$" + $tw.utils.escapeRegExp(param.name) + "\\$","mg"),param.value);
|
value = $tw.utils.replaceString(value,new RegExp("\\$" + $tw.utils.escapeRegExp(param.name) + "\\$","mg"),param.value);
|
||||||
});
|
});
|
||||||
value = this.substituteVariableReferences(value,options);
|
value = self.substituteVariableReferences(value,options);
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
text: value,
|
text: value,
|
||||||
params: params,
|
params: params,
|
||||||
srcVariable: variable,
|
srcVariable: variable,
|
||||||
isCacheable: originalValue === value
|
isCacheable: originalValue === value
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
// Check for the variable defined in the parent widget (or an ancestor in the prototype chain)
|
||||||
|
if(currWidget && name in currWidget.variables) {
|
||||||
|
return processVariable(currWidget.variables[name]);
|
||||||
}
|
}
|
||||||
// If the variable doesn't exist in the parent widget then look for a macro module
|
// If the variable doesn't exist in the parent widget then look for a macro module
|
||||||
|
var text = this.evaluateMacroModule(name,actualParams);
|
||||||
|
if(text === undefined) {
|
||||||
|
// Check for a shadow variable tiddler
|
||||||
|
var tiddler = this.wiki.getTiddler("$:/global/" + name);
|
||||||
|
if(tiddler) {
|
||||||
|
return processVariable({
|
||||||
|
value: tiddler.getFieldString("text"),
|
||||||
|
params: $tw.utils.parseParameterDefinition(tiddler.getFieldString("parameters"),{requireParenthesis: true}),
|
||||||
|
isMacroDefinition: tiddler.getFieldString("is-macro") === "yes",
|
||||||
|
isWidgetDefinition: tiddler.getFieldString("is-widget") === "yes",
|
||||||
|
isProcedureDefinition: tiddler.getFieldString("is-procedure") === "yes",
|
||||||
|
isFunctionDefinition: tiddler.getFieldString("is-function") === "yes"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(text === undefined) {
|
||||||
|
text = options.defaultValue;
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
text: this.evaluateMacroModule(name,actualParams,options.defaultValue),
|
text: text,
|
||||||
srcVariable: {}
|
srcVariable: {}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -479,12 +502,12 @@ Widget.prototype.makeChildWidget = function(parseTreeNode,options) {
|
||||||
options = options || {};
|
options = options || {};
|
||||||
// Check whether this node type is defined by a custom widget definition
|
// Check whether this node type is defined by a custom widget definition
|
||||||
var variableDefinitionName = "$" + parseTreeNode.type,
|
var variableDefinitionName = "$" + parseTreeNode.type,
|
||||||
variableInfo = this.variables[variableDefinitionName],
|
variableInfo = this.getVariableInfo(variableDefinitionName,{allowSelfAssigned: true}),
|
||||||
isOverrideable = function() {
|
isOverrideable = function() {
|
||||||
// Widget is overrideable if it has a double dollar user defined name, or if it is an existing JS widget
|
// Widget is overrideable if it has a double dollar user defined name, or if it is an existing JS widget
|
||||||
return parseTreeNode.type.charAt(0) === "$" || !!self.widgetClasses[parseTreeNode.type];
|
return parseTreeNode.type.charAt(0) === "$" || !!self.widgetClasses[parseTreeNode.type];
|
||||||
};
|
};
|
||||||
if(!parseTreeNode.isNotRemappable && isOverrideable() && variableInfo && variableInfo.value && variableInfo.isWidgetDefinition) {
|
if(!parseTreeNode.isNotRemappable && isOverrideable() && variableInfo && variableInfo.srcVariable && variableInfo.srcVariable.value && variableInfo.srcVariable.isWidgetDefinition) {
|
||||||
var newParseTreeNode = {
|
var newParseTreeNode = {
|
||||||
type: "transclude",
|
type: "transclude",
|
||||||
children: [
|
children: [
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
title: Globals/CustomWidget
|
||||||
|
description: Global shadow variable defining a custom widget
|
||||||
|
type: text/vnd.tiddlywiki-multiple
|
||||||
|
tags: [[$:/tags/wiki-test-spec]]
|
||||||
|
|
||||||
|
title: Output
|
||||||
|
|
||||||
|
\whitespace trim
|
||||||
|
|
||||||
|
<$$mywidget foo="Mahogany">
|
||||||
|
Sycamore!
|
||||||
|
</$$mywidget>
|
||||||
|
+
|
||||||
|
title: $:/global/$$mywidget
|
||||||
|
is-widget: yes
|
||||||
|
parameters: (foo:"bar")
|
||||||
|
|
||||||
|
Koala! <$text text=<<foo>>/>, <$slot $name="ts-body"/>
|
||||||
|
+
|
||||||
|
title: ExpectedResult
|
||||||
|
|
||||||
|
<p>Koala! Mahogany, Sycamore!</p>
|
|
@ -0,0 +1,22 @@
|
||||||
|
title: Globals/Functions
|
||||||
|
description: Global functions in shadow variables
|
||||||
|
type: text/vnd.tiddlywiki-multiple
|
||||||
|
tags: [[$:/tags/wiki-test-spec]]
|
||||||
|
|
||||||
|
title: Output
|
||||||
|
|
||||||
|
\whitespace trim
|
||||||
|
|
||||||
|
<$text text=<<this-is-one>>/>
|
||||||
|
|
|
||||||
|
<$text text=<<this-is-one 7>>/>
|
||||||
|
+
|
||||||
|
title: $:/global/this-is-one
|
||||||
|
is-function: yes
|
||||||
|
parameters: (foo:"2")
|
||||||
|
|
||||||
|
[<foo>multiply[2.5]]
|
||||||
|
+
|
||||||
|
title: ExpectedResult
|
||||||
|
|
||||||
|
<p>5|17.5</p>
|
|
@ -0,0 +1,27 @@
|
||||||
|
title: Globals/Procedures
|
||||||
|
description: Global procedures in shadow variables
|
||||||
|
type: text/vnd.tiddlywiki-multiple
|
||||||
|
tags: [[$:/tags/wiki-test-spec]]
|
||||||
|
|
||||||
|
title: Output
|
||||||
|
|
||||||
|
\whitespace trim
|
||||||
|
|
||||||
|
<<this-is-one>>
|
||||||
|
|
|
||||||
|
<<this-is-one blah>>
|
||||||
|
+
|
||||||
|
title: $:/global/this-is-one
|
||||||
|
|
||||||
|
\whitespace trim
|
||||||
|
|
||||||
|
\procedure example()
|
||||||
|
ONE
|
||||||
|
\end
|
||||||
|
|
||||||
|
\parameters (foo:"nothing")
|
||||||
|
<<example>>-<$text text=<<foo>>/>
|
||||||
|
+
|
||||||
|
title: ExpectedResult
|
||||||
|
|
||||||
|
<p>ONE-nothing</p><p>|ONE-blah</p>
|
|
@ -0,0 +1,26 @@
|
||||||
|
title: Globals/ProceduresWithConditionalDefinitions
|
||||||
|
description: Global procedures with conditional definitions to allow overriding
|
||||||
|
type: text/vnd.tiddlywiki-multiple
|
||||||
|
tags: [[$:/tags/wiki-test-spec]]
|
||||||
|
|
||||||
|
title: Output
|
||||||
|
|
||||||
|
\whitespace trim
|
||||||
|
|
||||||
|
<<this-is-one>>|<<this-is-one blah>>~
|
||||||
|
<$let example="TWO"><<this-is-one>>|<<this-is-one blah>></$let>
|
||||||
|
+
|
||||||
|
title: $:/global/this-is-one
|
||||||
|
|
||||||
|
\whitespace trim
|
||||||
|
|
||||||
|
\+procedure example()
|
||||||
|
ONE
|
||||||
|
\end
|
||||||
|
|
||||||
|
\parameters (foo:"nothing")
|
||||||
|
<<example>>-<$text text=<<foo>>/>
|
||||||
|
+
|
||||||
|
title: ExpectedResult
|
||||||
|
|
||||||
|
<p>ONE-nothing|ONE-blah~TWO-nothing|TWO-blah</p>
|
|
@ -114,7 +114,7 @@ describe("WikiText parser tests", function() {
|
||||||
it("should parse macro definitions", function() {
|
it("should parse macro definitions", function() {
|
||||||
expect(parse("\\define myMacro()\nnothing\n\\end\n")).toEqual(
|
expect(parse("\\define myMacro()\nnothing\n\\end\n")).toEqual(
|
||||||
|
|
||||||
[ { type : 'set', attributes : { name : { type : 'string', value : 'myMacro' }, value : { type : 'string', value : 'nothing' } }, children : [ ], params : [ ], isMacroDefinition : true } ]
|
[{"type":"set","attributes":{"name":{"name":"name","type":"string","value":"myMacro"},"value":{"name":"value","type":"string","value":"nothing"}},"children":[],"params":[],"isMacroDefinition":true,"orderedAttributes":[{"name":"name","type":"string","value":"myMacro"},{"name":"value","type":"string","value":"nothing"}]}]
|
||||||
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -185,7 +185,7 @@ describe("WikiText parser tests", function() {
|
||||||
it("should parse comment in pragma area. Comment will be invisible", function() {
|
it("should parse comment in pragma area. Comment will be invisible", function() {
|
||||||
expect(parse("<!-- comment in pragma area -->\n\\define aMacro()\nnothing\n\\end\n")).toEqual(
|
expect(parse("<!-- comment in pragma area -->\n\\define aMacro()\nnothing\n\\end\n")).toEqual(
|
||||||
|
|
||||||
[ { type : 'set', attributes : { name : { type : 'string', value : 'aMacro' }, value : { type : 'string', value : 'nothing' } }, children : [ ], params : [ ], isMacroDefinition : true } ]
|
[{"type":"set","attributes":{"name":{"name":"name","type":"string","value":"aMacro"},"value":{"name":"value","type":"string","value":"nothing"}},"children":[],"params":[],"isMacroDefinition":true,"orderedAttributes":[{"name":"name","type":"string","value":"aMacro"},{"name":"value","type":"string","value":"nothing"}]}]
|
||||||
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue