diff --git a/core/modules/parsers/wikiparser/rules/functiondef.js b/core/modules/parsers/wikiparser/rules/functiondef.js new file mode 100644 index 000000000..665b939f2 --- /dev/null +++ b/core/modules/parsers/wikiparser/rules/functiondef.js @@ -0,0 +1,93 @@ +/*\ +title: $:/core/modules/parsers/wikiparser/rules/functiondef.js +type: application/javascript +module-type: wikirule + +Wiki pragma rule for function definitions + +``` +\function name(param:defaultvalue,param2:defaultvalue) +definition text +\end +``` + +\*/ +(function(){ + +/*jslint node: true, browser: true */ +/*global $tw: false */ +"use strict"; + +exports.name = "functiondef"; +exports.types = {pragma: true}; + +/* +Instantiate parse rule +*/ +exports.init = function(parser) { + this.parser = parser; + // Regexp to match + this.matchRegExp = /^\\function\s+([^(\s]+)\(\s*([^)]*)\)(\s*\r?\n)?/mg; +}; + +/* +Parse the most recent match +*/ +exports.parse = function() { + // Move past the macro name and parameters + this.parser.pos = this.matchRegExp.lastIndex; + // Parse the parameters + var paramString = this.match[2], + params = []; + if(paramString !== "") { + var reParam = /\s*([A-Za-z0-9\-_]+)(?:\s*:\s*(?:"""([\s\S]*?)"""|"([^"]*)"|'([^']*)'|([^"'\s]+)))?/mg, + paramMatch = reParam.exec(paramString); + while(paramMatch) { + // Save the parameter details + var paramInfo = {name: paramMatch[1]}, + defaultValue = paramMatch[2] || paramMatch[3] || paramMatch[4] || paramMatch[5]; + if(defaultValue !== undefined) { + paramInfo["default"] = defaultValue; + } + params.push(paramInfo); + // Look for the next parameter + paramMatch = reParam.exec(paramString); + } + } + // Is this a multiline definition? + var reEnd; + if(this.match[3]) { + // If so, the end of the body is marked with \end + reEnd = /(\r?\n\\end[^\S\n\r]*(?:$|\r?\n))/mg; + } else { + // Otherwise, the end of the definition is marked by the end of the line + reEnd = /($|\r?\n)/mg; + // Move past any whitespace + this.parser.pos = $tw.utils.skipWhiteSpace(this.parser.source,this.parser.pos); + } + // Find the end of the definition + reEnd.lastIndex = this.parser.pos; + var text, + endMatch = reEnd.exec(this.parser.source); + if(endMatch) { + text = this.parser.source.substring(this.parser.pos,endMatch.index); + this.parser.pos = endMatch.index + endMatch[0].length; + } else { + // We didn't find the end of the definition, so we'll make it blank + text = ""; + } + // Save the macro definition + return [{ + type: "set", + attributes: { + name: {type: "string", value: this.match[1]}, + value: {type: "string", value: text} + }, + children: [], + variableParams: params, + isFunctionDefinition: true + }]; +}; + +})(); + \ No newline at end of file diff --git a/core/modules/widgets/setvariable.js b/core/modules/widgets/setvariable.js index cc97067c7..8a35ffada 100755 --- a/core/modules/widgets/setvariable.js +++ b/core/modules/widgets/setvariable.js @@ -48,7 +48,13 @@ SetWidget.prototype.execute = function() { this.setValue = this.getAttribute("value"); this.setEmptyValue = this.getAttribute("emptyValue"); // Set context variable - this.setVariable(this.setName,this.getValue(),this.parseTreeNode.params,!!this.parseTreeNode.isMacroDefinition); + if(this.parseTreeNode.isMacroDefinition) { + this.setVariable(this.setName,this.getValue(),this.parseTreeNode.params,!!this.parseTreeNode.isMacroDefinition); + } else if(this.parseTreeNode.isFunctionDefinition) { + this.setVariable(this.setName,this.getValue(),undefined,undefined,{isFunctionDefinition: this.parseTreeNode.isFunctionDefinition,variableParams: this.parseTreeNode.variableParams}); + } else { + this.setVariable(this.setName,this.getValue()); + } // Construct the child widgets this.makeChildWidgets(); }; diff --git a/core/modules/widgets/transclude.js b/core/modules/widgets/transclude.js index d8057da5a..064e6795b 100755 --- a/core/modules/widgets/transclude.js +++ b/core/modules/widgets/transclude.js @@ -1,3 +1,5 @@ +const { parse } = require("../parsers/wikiparser/rules/transcludeinline"); + /*\ title: $:/core/modules/widgets/transclude.js type: application/javascript @@ -169,7 +171,26 @@ TranscludeWidget.prototype.getTransclusionTarget = function() { } var parser; if(this.transcludeVariable) { - parser = this.wiki.parseText(this.transcludeType,this.getVariable(this.transcludeVariable,""),{parseAsInline: !this.parseTreeNode.isBlock}); + var variableInfo = this.getVariableInfo(this.transcludeVariable).srcVariable; + parser = this.wiki.parseText(this.transcludeType,variableInfo.value || "",{parseAsInline: !this.parseTreeNode.isBlock}); + if(parser && variableInfo.isFunctionDefinition) { + parser = { + tree: [ + { + type: "parameters", + name: "$parameters", + children: parser.tree, + attributes: {}, + orderedAttributes: [] + } + ] + } + $tw.utils.each(variableInfo.variableParams,function(param,index) { + var attr = {name: param.name, type: "string", value: param["default"]}; + parser.tree[0].attributes[param.name] = attr; + parser.tree[0].orderedAttributes.push(attr); + }); + } } else { parser = this.wiki.parseTextReference( this.transcludeTitle, diff --git a/core/modules/widgets/widget.js b/core/modules/widgets/widget.js index 2d3db8cf0..62a1d05a6 100755 --- a/core/modules/widgets/widget.js +++ b/core/modules/widgets/widget.js @@ -88,9 +88,19 @@ name: name of the variable value: value of the variable params: array of {name:, default:} for each parameter isMacroDefinition: true if the variable is set via a \define macro pragma (and hence should have variable substitution performed) +options includes: + isFunctionDefinition: true if the variable is set via a \function pragma (and hence should not have variable substitution performed) + variableParams: array of {name:, default:} for each function parameter */ -Widget.prototype.setVariable = function(name,value,params,isMacroDefinition) { - this.variables[name] = {value: value, params: params, isMacroDefinition: !!isMacroDefinition}; +Widget.prototype.setVariable = function(name,value,params,isMacroDefinition,options) { + options = options || {}; + this.variables[name] = { + value: value, + params: params, + isMacroDefinition: !!isMacroDefinition, + isFunctionDefinition: !!options.isFunctionDefinition, + variableParams: options.variableParams + }; }; /* diff --git a/editions/test/tiddlers/tests/data/transclude/Parameterised-Shortcut.tid b/editions/test/tiddlers/tests/data/transclude/Parameterised-Shortcut.tid new file mode 100644 index 000000000..a183fe2d4 --- /dev/null +++ b/editions/test/tiddlers/tests/data/transclude/Parameterised-Shortcut.tid @@ -0,0 +1,18 @@ +title: Transclude/Parameterised/Shortcut +description: Simple parameterised transclusion +type: text/vnd.tiddlywiki-multiple +tags: [[$:/tags/wiki-test-spec]] + +title: Output + +\whitespace trim +\function test(one:'Jaguar') +{<$text text=<>/>} +\end + +<$transclude $variable='test' one='Ferret'/> +<$transclude $variable='test'/> +_ +title: ExpectedResult + +

{Ferret}{Jaguar}

\ No newline at end of file diff --git a/editions/test/tiddlers/tests/test-wikitext-parser.js b/editions/test/tiddlers/tests/test-wikitext-parser.js index 7f1551c28..7daf3e76f 100644 --- a/editions/test/tiddlers/tests/test-wikitext-parser.js +++ b/editions/test/tiddlers/tests/test-wikitext-parser.js @@ -119,10 +119,26 @@ describe("WikiText parser tests", function() { ); }); + it("should parse function definitions", function() { + expect(parse("\\function myMacro(one,two,three,four:elephant)\nnothing\n\\end\n")).toEqual( + + [ { type : 'set', attributes : { name : { type : 'string', value : 'myMacro' }, value : { type : 'string', value : 'nothing' } }, children : [ ], variableParams : [ { name: 'one' }, { name: 'two' }, { name: 'three' }, { name: 'four', default: 'elephant' } ], isFunctionDefinition : true } ] + + ); + }); + + it("should parse function definitions", function() { + expect(parse("\\function myMacro(one:'Jaguar')\n<$text text=<>/>\n\\end\n\n")).toEqual( + + [ { type : 'set', attributes : { name : { type : 'string', value : 'myMacro' }, value : { type : 'string', value : '<$text text=<>/>' } }, children : [ ], variableParams : [ { name: 'one', "default": 'Jaguar' } ], isFunctionDefinition : true } ] + + ); + }); + it("should parse comment in pragma area. Comment will be invisible", function() { expect(parse("\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 : { type : 'string', value : 'aMacro' }, value : { type : 'string', value : 'nothing' } }, children : [ ], params : [ ], isMacroDefinition : true } ] ); });