From ef92b899ed85cd6d6c23d0f8b2080bbac16cedbf Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Fri, 11 Apr 2025 13:33:09 +0100 Subject: [PATCH] Add remote inspection of preconfigured filters There is no user interface yet, and it is currently hardcoded to inspect the filter "[all[shadows+tiddlers]tag[$:/tags/ViewTemplate]!is[draft]]" which is used in tiddler rendering. You can see the results in the sidebar More -> System starting with "$:/temp/filter-inspection/" --- boot/boot.js | 9 +- core/modules/filters.js | 14 +- .../internals/cascades/viewtemplatebody.tid | 5 + .../tiddlywiki/internals/inspect-filter.tid | 42 ++--- .../tiddlywiki/internals/modules/inspect.js | 80 ++------- .../tiddlywiki/internals/modules/startup.js | 158 ++++++++++++++++++ .../internals/templates/viewtemplatebody.tid | 3 + 7 files changed, 216 insertions(+), 95 deletions(-) create mode 100644 plugins/tiddlywiki/internals/cascades/viewtemplatebody.tid create mode 100644 plugins/tiddlywiki/internals/modules/startup.js create mode 100644 plugins/tiddlywiki/internals/templates/viewtemplatebody.tid diff --git a/boot/boot.js b/boot/boot.js index 11286678d..e3538a2fe 100644 --- a/boot/boot.js +++ b/boot/boot.js @@ -2732,14 +2732,15 @@ $tw.hooks.removeHook = function(hookName,definition) { /* Invoke the hook by key */ -$tw.hooks.invokeHook = function(hookName /*, value,... */) { - var args = Array.prototype.slice.call(arguments,1); - if($tw.utils.hop($tw.hooks.names,hookName)) { +$tw.hooks.invokeHook = function(hookName, firstArgument /*, value,... */) { + if(Object.prototype.hasOwnProperty.call($tw.hooks.names,hookName)) { + var args = Array.prototype.slice.call(arguments,1); for(var i = 0; i < $tw.hooks.names[hookName].length; i++) { args[0] = $tw.hooks.names[hookName][i].apply(null,args); } + return args[0]; } - return args[0]; + return firstArgument; }; /////////////////////////// Main boot function to decrypt tiddlers and then startup diff --git a/core/modules/filters.js b/core/modules/filters.js index d8edb7238..15476855c 100644 --- a/core/modules/filters.js +++ b/core/modules/filters.js @@ -231,11 +231,14 @@ exports.compileFilter = function(filterString,options) { options = options || {}; var self = this; var wrappers = options.wrappers || {}; + // Invoke the hook to allow the filter to be inspected + wrappers = $tw.hooks.invokeHook("th-filter-evaluation",filterString,wrappers) || wrappers; + // Get the result from the cache if we can if(!this.filterCache) { this.filterCache = Object.create(null); this.filterCacheCount = 0; } - if(this.filterCache[filterString] !== undefined && !wrappers.prefix && !wrappers.operation && !wrappers.operator) { + if(this.filterCache[filterString] !== undefined && !wrappers.prefix && !wrappers.operation && !wrappers.operator && !wrappers.start && !wrappers.done) { return this.filterCache[filterString]; } var filterParseTree; @@ -379,6 +382,9 @@ exports.compileFilter = function(filterString,options) { if(!widget) { widget = $tw.rootWidget; } + if(wrappers.start) { + wrappers.start(source); + } var results = new $tw.utils.LinkedList(); self.filterRecursionCount = (self.filterRecursionCount || 0) + 1; if(self.filterRecursionCount < MAX_FILTER_DEPTH) { @@ -389,7 +395,11 @@ exports.compileFilter = function(filterString,options) { results.push("/**-- Excessive filter recursion --**/"); } self.filterRecursionCount = self.filterRecursionCount - 1; - return results.toArray(); + var resultsArray = results.toArray(); + if(wrappers.done) { + wrappers.done(resultsArray); + } + return resultsArray; }); if(this.filterCacheCount >= 2000) { // To prevent memory leak, we maintain an upper limit for cache size. diff --git a/plugins/tiddlywiki/internals/cascades/viewtemplatebody.tid b/plugins/tiddlywiki/internals/cascades/viewtemplatebody.tid new file mode 100644 index 000000000..7f5a4cef0 --- /dev/null +++ b/plugins/tiddlywiki/internals/cascades/viewtemplatebody.tid @@ -0,0 +1,5 @@ +title: $:/plugins/internals/cascades/viewtemplatebody +tags: $:/tags/ViewTemplateBodyFilter +list-before: + +[tag[$:/tags/FilterInspectionOutput]type[application/json]then[$:/plugins/internals/templates/viewtemplatebody]] \ No newline at end of file diff --git a/plugins/tiddlywiki/internals/inspect-filter.tid b/plugins/tiddlywiki/internals/inspect-filter.tid index 13e55f594..7719675c0 100644 --- a/plugins/tiddlywiki/internals/inspect-filter.tid +++ b/plugins/tiddlywiki/internals/inspect-filter.tid @@ -141,26 +141,28 @@ tags: $:/tags/Macro \end inspect-run -\procedure inspect-filter(filter,inputFilter:"[all[tiddlers]]",orientation:"horizontal") -<$let json={{{ [subfilterinspect] }}}> - <$scrollable bind={{{ [addsuffix] }}}> -
match[horizontal]then[tc-inspect-filter-box-horizontal]] +[join[ ]] }}}> -
-
- Filter - <$text text={{{ [jsonget[inputFilter]] }}}/> -
-
- <$transclude $variable="inspect-list" jsonList={{{ [jsonextract[input]] }}} class="tc-box tc-inspect-input-box"/> - <$list filter="[jsonindexes[runs]nsort[]]" variable="indexRun"> - <$let transclusion={{{ [[run-]addsuffix] }}}> - <$transclude $variable="inspect-run" jsonRun={{{ [jsonextract[runs],] }}}/> - - - <$transclude $variable="inspect-list" jsonList={{{ [jsonextract[output]] }}} class="tc-box tc-inspect-output-box"/> -
+\procedure inspect-filter-output(jsonOutput,orientation:"horizontal") +<$scrollable bind={{{ [addsuffix] }}}> +
match[horizontal]then[tc-inspect-filter-box-horizontal]] +[join[ ]] }}}> +
+
+ Filter + <$text text={{{ [jsonget[inputFilter]] }}}/> +
+
+ <$transclude $variable="inspect-list" jsonList={{{ [jsonextract[input]] }}} class="tc-box tc-inspect-input-box"/> + <$list filter="[jsonindexes[runs]nsort[]]" variable="indexRun"> + <$let transclusion={{{ [[run-]addsuffix] }}}> + <$transclude $variable="inspect-run" jsonRun={{{ [jsonextract[runs],] }}}/> + + + <$transclude $variable="inspect-list" jsonList={{{ [jsonextract[output]] }}} class="tc-box tc-inspect-output-box"/>
- - +
+ +\end inspect-filter-output + +\procedure inspect-filter(filter,inputFilter:"[all[tiddlers]]",orientation:"horizontal") +<$transclude $variable="inspect-filter-output" jsonOutput={{{ [subfilterinspect] }}} orientation=<> /> \end inspect-filter diff --git a/plugins/tiddlywiki/internals/modules/inspect.js b/plugins/tiddlywiki/internals/modules/inspect.js index 2ff8c2cc0..110f554af 100644 --- a/plugins/tiddlywiki/internals/modules/inspect.js +++ b/plugins/tiddlywiki/internals/modules/inspect.js @@ -9,79 +9,21 @@ Filter operator for inspecting the evaluation of a filter "use strict"; +var getWrappers = $tw.plugins.internals.getWrappers; + /* Export our filter function */ exports.inspect = function(source,operator,options) { - var self = this, - inputFilter = operator.operands[0] || "", - output = { - input: [], - runs: [], - inputFilter: inputFilter - }, - currentRun,currentOperation; - // Save the input - source(function(tiddler,title) { - output.input.push(title); - }); + var inputFilter = operator.operands[0] || "", + results, + wrappers = getWrappers(function(output) { + results = output; + },inputFilter); // Compile the filter with wrapper functions to log the details var compiledFilter = options.wiki.compileFilter(inputFilter,{ - wrappers: { - prefix: function(filterRunPrefixFunction,operationFunction,innerOptions) { - return function(results,innerSource,innerWidget) { - var details ={ - input: results.toArray(), - prefixName: innerOptions.prefixName, - suffixes: innerOptions.suffixes, - operations: [] - }; - currentRun = details.operations; - var innerResults = filterRunPrefixFunction.call(null,operationFunction,innerOptions); - innerResults(results,innerSource,innerWidget); - details.output = results.toArray(); - output.runs.push(details); - }; - }, - operation: function(operationFunction,operation) { - var details = { - operators: [] - } - currentOperation = details.operators; - currentRun.push(details); - operationFunction(); - }, - operator: function(operatorFunction,innerSource,innerOperator,innerOptions) { - var details = { - operatorName: innerOperator.operatorName, - operands: innerOperator.operands, - parseTree: innerOperator.parseTree, - prefix: innerOperator.prefix, - suffix: innerOperator.suffix, - suffixes: innerOperator.suffixes, - regexp: innerOperator.regexp, - input: [] - }; - innerSource(function(tiddler,title) { - details.input.push(title); - }); - currentOperation.push(details); - var innerResults = operatorFunction.apply(null,Array.prototype.slice.call(arguments,1)); - if(!$tw.utils.isArray(innerResults)) { - var resultArray = []; - innerResults(function(tiddler,title) { - resultArray.push(title); - }); - innerResults = resultArray; - } - details.output = innerResults; - return innerResults; - } - } + wrappers: wrappers }); - output.output = compiledFilter.call(options.wiki,source,options.widget); - var results = []; - // console.log(`Inspected ${JSON.stringify(output,null,4)}`); - results.push(JSON.stringify(output,null,4)); - return results; -}; + compiledFilter.call(options.wiki,source,options.widget); + return [JSON.stringify(results)]; +}; \ No newline at end of file diff --git a/plugins/tiddlywiki/internals/modules/startup.js b/plugins/tiddlywiki/internals/modules/startup.js new file mode 100644 index 000000000..8366d416e --- /dev/null +++ b/plugins/tiddlywiki/internals/modules/startup.js @@ -0,0 +1,158 @@ +/*\ +title: $:/plugins/tiddlywiki/internals/startup.js +type: application/javascript +module-type: startup + +Install hooks + +\*/ + +"use strict"; + +// Export name and synchronous status +exports.name = "internals-plugin"; +exports.before = ["startup"]; +exports.synchronous = true; + +exports.startup = function() { + $tw.plugins = $tw.plugins || {}; + $tw.plugins.internals = { + getWrappers: getWrappers + }; + var inspectedFilters = [ + "[all[shadows+tiddlers]tag[$:/tags/ViewTemplate]!is[draft]]" + ], + accumulator = []; + $tw.hooks.addHook("th-filter-evaluation",function(filterString,wrappers) { + // Check whether this is a filter we want to inspect + if(inspectedFilters.indexOf(filterString) === -1) { + return wrappers; + } + // Don't do anything if there are already wrappers + if(wrappers.prefix || wrappers.operation || wrappers.operator) { + return wrappers; + } + var flushAccumulator = function() { + if(accumulator.length) { + $tw.utils.each(accumulator,function(jsonInspectionOutput) { + // Get the output as a string + var stringInspectionOutput = JSON.stringify(jsonInspectionOutput), + logPrefix = `$:/temp/filter-inspection/${ + $tw.utils.stringifyDate(new Date())}: `, + logPrefixLength = logPrefix.length; + // Check if the the output is the same as the last log with the same filter + var logTitles = []; + $tw.wiki.each(function(tiddler,title) { + if(title.substring(logPrefixLength) === filterString) { + logTitles.push(title); + } + }); + var alreadyLogged = false; + $tw.utils.each(logTitles,function(title) { + var jsonLog = $tw.wiki.getTiddlerData(title); + if($tw.utils.isArrayEqual(jsonLog.output,jsonInspectionOutput.output)) { + alreadyLogged = true; + } + }); + if(alreadyLogged) { + return; + } + // Add the log tiddler + var tiddlerFields = { + title: `${logPrefix}${filterString}`, + tags: ["$:/tags/FilterInspectionOutput"], + text: stringInspectionOutput, + filter: filterString, + type: "application/json" + } + // console.log("Adding " + JSON.stringify(tiddlerFields)); + $tw.wiki.addTiddler(new $tw.Tiddler($tw.wiki.getCreationFields(),$tw.wiki.getModificationFields(),tiddlerFields)); + }); + accumulator = []; + } + }; + // Get our wrappers + return getWrappers(function(output) { + // Schedule a flush + if(accumulator.length === 0) { + $tw.utils.nextTick(function() { + flushAccumulator(); + }); + } + accumulator.push(output); + },filterString); + }); +}; + +function getWrappers(fnDone,inputFilter) { + var output = { + inputFilter: inputFilter, + input: [], + runs: [] + }, + currentRun,currentOperation; + // Compile the filter with wrapper functions to log the details + return { + start: function(source) { + // Save the input + source(function(tiddler,title) { + output.input.push(title); + }); + }, + prefix: function(filterRunPrefixFunction,operationFunction,innerOptions) { + return function(results,innerSource,innerWidget) { + var details ={ + input: results.toArray(), + prefixName: innerOptions.prefixName, + suffixes: innerOptions.suffixes, + operations: [] + }; + currentRun = details.operations; + var innerResults = filterRunPrefixFunction.call(null,operationFunction,innerOptions); + innerResults(results,innerSource,innerWidget); + details.output = results.toArray(); + output.runs.push(details); + }; + }, + operation: function(operationFunction,operation) { + var details = { + operators: [] + } + currentOperation = details.operators; + currentRun.push(details); + operationFunction(); + }, + operator: function(operatorFunction,innerSource,innerOperator,innerOptions) { + var details = { + operatorName: innerOperator.operatorName, + operands: innerOperator.operands, + parseTree: innerOperator.parseTree, + prefix: innerOperator.prefix, + suffix: innerOperator.suffix, + suffixes: innerOperator.suffixes, + regexp: innerOperator.regexp, + input: [] + }; + innerSource(function(tiddler,title) { + details.input.push(title); + }); + currentOperation.push(details); + var innerResults = operatorFunction.apply(null,Array.prototype.slice.call(arguments,1)); + if(!$tw.utils.isArray(innerResults)) { + var resultArray = []; + innerResults(function(tiddler,title) { + resultArray.push(title); + }); + innerResults = resultArray; + } + details.output = innerResults; + return innerResults; + }, + done: function(results) { + output.output = results; + // console.log(`Inspected ${JSON.stringify(output,null,4)}`); + if(fnDone) { + fnDone(output); + } + } + }; +} diff --git a/plugins/tiddlywiki/internals/templates/viewtemplatebody.tid b/plugins/tiddlywiki/internals/templates/viewtemplatebody.tid new file mode 100644 index 000000000..054a0e697 --- /dev/null +++ b/plugins/tiddlywiki/internals/templates/viewtemplatebody.tid @@ -0,0 +1,3 @@ +title: $:/plugins/internals/templates/viewtemplatebody + +<$transclude $variable="inspect-filter-output" jsonOutput={{!!text}} />