1
0
mirror of https://github.com/Jermolene/TiddlyWiki5 synced 2026-01-02 21:49:04 +00:00

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/"
This commit is contained in:
Jeremy Ruston
2025-04-11 13:33:09 +01:00
parent dcb8fa2f86
commit ef92b899ed
7 changed files with 216 additions and 95 deletions

View File

@@ -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

View File

@@ -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.

View File

@@ -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]]

View File

@@ -141,26 +141,28 @@ tags: $:/tags/Macro
</div>
\end inspect-run
\procedure inspect-filter(filter,inputFilter:"[all[tiddlers]]",orientation:"horizontal")
<$let json={{{ [subfilter<inputFilter>inspect<filter>] }}}>
<$scrollable bind={{{ [<qualify "$:/temp/filter-inspector/">addsuffix<filter>] }}}>
<div class={{{ tc-inspect-filter-box [<orientation>match[horizontal]then[tc-inspect-filter-box-horizontal]] +[join[ ]] }}}>
<div class="tc-box">
<div class="tc-box-header">
Filter
<span class="tc-pill"><$text text={{{ [<json>jsonget[inputFilter]] }}}/></span>
</div>
<div class="tc-box-content">
<$transclude $variable="inspect-list" jsonList={{{ [<json>jsonextract[input]] }}} class="tc-box tc-inspect-input-box"/>
<$list filter="[<json>jsonindexes[runs]nsort[]]" variable="indexRun">
<$let transclusion={{{ [[run-]addsuffix<indexRun>] }}}>
<$transclude $variable="inspect-run" jsonRun={{{ [<json>jsonextract[runs],<indexRun>] }}}/>
</$let>
</$list>
<$transclude $variable="inspect-list" jsonList={{{ [<json>jsonextract[output]] }}} class="tc-box tc-inspect-output-box"/>
</div>
\procedure inspect-filter-output(jsonOutput,orientation:"horizontal")
<$scrollable bind={{{ [<qualify "$:/temp/filter-inspector/">addsuffix<filter>] }}}>
<div class={{{ tc-inspect-filter-box [<orientation>match[horizontal]then[tc-inspect-filter-box-horizontal]] +[join[ ]] }}}>
<div class="tc-box">
<div class="tc-box-header">
Filter
<span class="tc-pill"><$text text={{{ [<jsonOutput>jsonget[inputFilter]] }}}/></span>
</div>
<div class="tc-box-content">
<$transclude $variable="inspect-list" jsonList={{{ [<jsonOutput>jsonextract[input]] }}} class="tc-box tc-inspect-input-box"/>
<$list filter="[<jsonOutput>jsonindexes[runs]nsort[]]" variable="indexRun">
<$let transclusion={{{ [[run-]addsuffix<indexRun>] }}}>
<$transclude $variable="inspect-run" jsonRun={{{ [<jsonOutput>jsonextract[runs],<indexRun>] }}}/>
</$let>
</$list>
<$transclude $variable="inspect-list" jsonList={{{ [<jsonOutput>jsonextract[output]] }}} class="tc-box tc-inspect-output-box"/>
</div>
</div>
</$scrollable>
</$let>
</div>
</$scrollable>
\end inspect-filter-output
\procedure inspect-filter(filter,inputFilter:"[all[tiddlers]]",orientation:"horizontal")
<$transclude $variable="inspect-filter-output" jsonOutput={{{ [subfilter<inputFilter>inspect<filter>] }}} orientation=<<orientation>> />
\end inspect-filter

View File

@@ -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)];
};

View File

@@ -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);
}
}
};
}

View File

@@ -0,0 +1,3 @@
title: $:/plugins/internals/templates/viewtemplatebody
<$transclude $variable="inspect-filter-output" jsonOutput={{!!text}} />