mirror of
https://github.com/Jermolene/TiddlyWiki5.git
synced 2026-04-27 15:50:53 -07:00
feat: filter serialize CST quote round-trip, formatting options, mvvdisplayinline
- Add titleQuote CST metadata to filter parser for double/single/unquoted shorthand title syntax, enabling lossless round-trip serialization - serializeFilterParseTree() now accepts maxRunsPerLine / wrapAt / indent options for formatter/linter support - Add serializeFilterParseTree() utility (filter.js) to wikitext-serialize plugin - Add mvvdisplayinline serializer rule to wikitext-serialize plugin - Add tests for all of the above - Add 5.5.0 release note folder and change note (PR number TBD)
This commit is contained in:
parent
b0d99f3bd3
commit
f4799ed6af
7 changed files with 474 additions and 2 deletions
|
|
@ -203,9 +203,16 @@ exports.parseFilter = function(filterString) {
|
|||
p = parseFilterOperation(operation.operators,filterString,p);
|
||||
}
|
||||
// Quoted strings and unquoted title
|
||||
if(match[5] || match[6] || match[7]) { // Double quoted string, single quoted string or unquoted title
|
||||
// Preserve the original quote style as CST metadata on the operator so
|
||||
// that a serializer can round-trip the original source without normalizing.
|
||||
// match[5] = double-quoted, match[6] = single-quoted, match[7] = unquoted
|
||||
// The original condition (match[5] || match[6] || match[7]) is kept so that
|
||||
// empty quoted titles "" / '' continue to be ignored, preserving existing behaviour.
|
||||
if(match[5] || match[6] || match[7]) {
|
||||
var titleText = match[5] || match[6] || match[7];
|
||||
var titleQuote = match[5] !== undefined ? "double" : (match[6] !== undefined ? "single" : "none");
|
||||
operation.operators.push(
|
||||
{operator: "title", operands: [{text: match[5] || match[6] || match[7]}]}
|
||||
{operator: "title", operands: [{text: titleText}], titleQuote: titleQuote}
|
||||
);
|
||||
}
|
||||
results.push(operation);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
tags: $:/tags/wikitext-serialize-test-spec
|
||||
title: Serialize/MvvDisplayInline
|
||||
type: text/vnd.tiddlywiki
|
||||
|
||||
((myVar))
|
||||
((myVar||; ))
|
||||
((([tag[foo]])))
|
||||
((([tag[foo]]||; )))
|
||||
245
editions/test/tiddlers/tests/test-filter-serialize.js
Normal file
245
editions/test/tiddlers/tests/test-filter-serialize.js
Normal file
|
|
@ -0,0 +1,245 @@
|
|||
/*\
|
||||
title: test-filter-serialize.js
|
||||
type: application/javascript
|
||||
tags: [[$:/tags/test-spec]]
|
||||
|
||||
Tests the filter expression serialization from filter AST.
|
||||
|
||||
\*/
|
||||
|
||||
describe("Filter serialization unit tests", function () {
|
||||
|
||||
it("should serialize simple operator", function () {
|
||||
var filter = "[tag[docs]]";
|
||||
var tree = $tw.wiki.parseFilter(filter);
|
||||
var serialized = $tw.utils.serializeFilterParseTree(tree);
|
||||
expect(serialized).toBe("[tag[docs]]");
|
||||
});
|
||||
|
||||
it("should serialize negated operator", function () {
|
||||
var filter = "[!tag[docs]]";
|
||||
var tree = $tw.wiki.parseFilter(filter);
|
||||
var serialized = $tw.utils.serializeFilterParseTree(tree);
|
||||
expect(serialized).toBe("[!tag[docs]]");
|
||||
});
|
||||
|
||||
it("should serialize chained operators", function () {
|
||||
var filter = "[tag[docs]sort[title]]";
|
||||
var tree = $tw.wiki.parseFilter(filter);
|
||||
var serialized = $tw.utils.serializeFilterParseTree(tree);
|
||||
expect(serialized).toBe("[tag[docs]sort[title]]");
|
||||
});
|
||||
|
||||
it("should serialize multiple runs", function () {
|
||||
var filter = "[tag[docs]] [tag[other]]";
|
||||
var tree = $tw.wiki.parseFilter(filter);
|
||||
var serialized = $tw.utils.serializeFilterParseTree(tree);
|
||||
expect(serialized).toBe("[tag[docs]] [tag[other]]");
|
||||
});
|
||||
|
||||
it("should serialize + prefix", function () {
|
||||
var filter = "[tag[docs]] +[sort[title]]";
|
||||
var tree = $tw.wiki.parseFilter(filter);
|
||||
var serialized = $tw.utils.serializeFilterParseTree(tree);
|
||||
expect(serialized).toBe("[tag[docs]] +[sort[title]]");
|
||||
});
|
||||
|
||||
it("should serialize - prefix", function () {
|
||||
var filter = "[tag[docs]] -[tag[exclude]]";
|
||||
var tree = $tw.wiki.parseFilter(filter);
|
||||
var serialized = $tw.utils.serializeFilterParseTree(tree);
|
||||
expect(serialized).toBe("[tag[docs]] -[tag[exclude]]");
|
||||
});
|
||||
|
||||
it("should serialize ~ prefix", function () {
|
||||
var filter = "[tag[docs]] ~[tag[fallback]]";
|
||||
var tree = $tw.wiki.parseFilter(filter);
|
||||
var serialized = $tw.utils.serializeFilterParseTree(tree);
|
||||
expect(serialized).toBe("[tag[docs]] ~[tag[fallback]]");
|
||||
});
|
||||
|
||||
it("should serialize => prefix", function () {
|
||||
var filter = "=>[sum[]]";
|
||||
var tree = $tw.wiki.parseFilter(filter);
|
||||
var serialized = $tw.utils.serializeFilterParseTree(tree);
|
||||
expect(serialized).toBe("=>[sum[]]");
|
||||
});
|
||||
|
||||
it("should serialize = prefix", function () {
|
||||
var filter = "=[tag[docs]]";
|
||||
var tree = $tw.wiki.parseFilter(filter);
|
||||
var serialized = $tw.utils.serializeFilterParseTree(tree);
|
||||
expect(serialized).toBe("=[tag[docs]]");
|
||||
});
|
||||
|
||||
it("should serialize named prefix :filter", function () {
|
||||
var filter = "[tag[docs]] :filter[get[text]length[]compare:integer:gteq[100]]";
|
||||
var tree = $tw.wiki.parseFilter(filter);
|
||||
var serialized = $tw.utils.serializeFilterParseTree(tree);
|
||||
expect(serialized).toBe("[tag[docs]] :filter[get[text]length[]compare:integer:gteq[100]]");
|
||||
});
|
||||
|
||||
it("should serialize operator suffix with single part", function () {
|
||||
var filter = "[search:title,text[foo]]";
|
||||
var tree = $tw.wiki.parseFilter(filter);
|
||||
var serialized = $tw.utils.serializeFilterParseTree(tree);
|
||||
expect(serialized).toBe("[search:title,text[foo]]");
|
||||
});
|
||||
|
||||
it("should serialize operator suffix with multiple parts", function () {
|
||||
var filter = "[search:title:literal,casesensitive[hello]]";
|
||||
var tree = $tw.wiki.parseFilter(filter);
|
||||
var serialized = $tw.utils.serializeFilterParseTree(tree);
|
||||
expect(serialized).toBe("[search:title:literal,casesensitive[hello]]");
|
||||
});
|
||||
|
||||
it("should serialize indirect operand {}", function () {
|
||||
var filter = "[title{CurrentTiddler}]";
|
||||
var tree = $tw.wiki.parseFilter(filter);
|
||||
var serialized = $tw.utils.serializeFilterParseTree(tree);
|
||||
expect(serialized).toBe("[title{CurrentTiddler}]");
|
||||
});
|
||||
|
||||
it("should serialize variable operand <>", function () {
|
||||
var filter = "[tag<myVar>]";
|
||||
var tree = $tw.wiki.parseFilter(filter);
|
||||
var serialized = $tw.utils.serializeFilterParseTree(tree);
|
||||
expect(serialized).toBe("[tag<myVar>]");
|
||||
});
|
||||
|
||||
it("should serialize multi-valued variable operand ()", function () {
|
||||
var filter = "[tag(myMVV)]";
|
||||
var tree = $tw.wiki.parseFilter(filter);
|
||||
var serialized = $tw.utils.serializeFilterParseTree(tree);
|
||||
expect(serialized).toBe("[tag(myMVV)]");
|
||||
});
|
||||
|
||||
it("should serialize multiple operands", function () {
|
||||
var filter = "[operator[a],[b]]";
|
||||
var tree = $tw.wiki.parseFilter(filter);
|
||||
var serialized = $tw.utils.serializeFilterParseTree(tree);
|
||||
expect(serialized).toBe("[operator[a],[b]]");
|
||||
});
|
||||
|
||||
it("should serialize mixed operand types", function () {
|
||||
var filter = "[operator[a],{b},<c>,(d)]";
|
||||
var tree = $tw.wiki.parseFilter(filter);
|
||||
var serialized = $tw.utils.serializeFilterParseTree(tree);
|
||||
expect(serialized).toBe("[operator[a],{b},<c>,(d)]");
|
||||
});
|
||||
|
||||
it("should serialize empty operand", function () {
|
||||
var filter = "[length[]]";
|
||||
var tree = $tw.wiki.parseFilter(filter);
|
||||
var serialized = $tw.utils.serializeFilterParseTree(tree);
|
||||
expect(serialized).toBe("[length[]]");
|
||||
});
|
||||
|
||||
it("should serialize empty filter", function () {
|
||||
var tree = $tw.wiki.parseFilter("");
|
||||
var serialized = $tw.utils.serializeFilterParseTree(tree);
|
||||
expect(serialized).toBe("");
|
||||
});
|
||||
|
||||
it("should serialize complex real-world filter", function () {
|
||||
var filter = "[all[tiddlers]!is[system]sort[title]limit[20]]";
|
||||
var tree = $tw.wiki.parseFilter(filter);
|
||||
var serialized = $tw.utils.serializeFilterParseTree(tree);
|
||||
expect(serialized).toBe("[all[tiddlers]!is[system]sort[title]limit[20]]");
|
||||
});
|
||||
|
||||
it("should handle null/undefined input", function () {
|
||||
expect($tw.utils.serializeFilterParseTree(null)).toBe("");
|
||||
expect($tw.utils.serializeFilterParseTree(undefined)).toBe("");
|
||||
});
|
||||
|
||||
it("should serialize named prefix with suffixes", function () {
|
||||
var filter = ":reduce:flat[add[]]";
|
||||
var tree = $tw.wiki.parseFilter(filter);
|
||||
var serialized = $tw.utils.serializeFilterParseTree(tree);
|
||||
expect(serialized).toBe(":reduce:flat[add[]]");
|
||||
});
|
||||
|
||||
// --- CST round-trip tests for shorthand title syntax ---
|
||||
|
||||
it("should round-trip unquoted title (CST: none)", function () {
|
||||
var filter = "MyTitle";
|
||||
var tree = $tw.wiki.parseFilter(filter);
|
||||
expect(tree[0].operators[0].titleQuote).toBe("none");
|
||||
var serialized = $tw.utils.serializeFilterParseTree(tree);
|
||||
expect(serialized).toBe("MyTitle");
|
||||
});
|
||||
|
||||
it("should round-trip double-quoted title (CST: double)", function () {
|
||||
var filter = "\"My Title\"";
|
||||
var tree = $tw.wiki.parseFilter(filter);
|
||||
expect(tree[0].operators[0].titleQuote).toBe("double");
|
||||
var serialized = $tw.utils.serializeFilterParseTree(tree);
|
||||
expect(serialized).toBe("\"My Title\"");
|
||||
});
|
||||
|
||||
it("should round-trip single-quoted title (CST: single)", function () {
|
||||
var filter = "'My Title'";
|
||||
var tree = $tw.wiki.parseFilter(filter);
|
||||
expect(tree[0].operators[0].titleQuote).toBe("single");
|
||||
var serialized = $tw.utils.serializeFilterParseTree(tree);
|
||||
expect(serialized).toBe("'My Title'");
|
||||
});
|
||||
|
||||
it("should NOT set titleQuote on explicit bracket form [title[...]]", function () {
|
||||
var filter = "[title[MyTitle]]";
|
||||
var tree = $tw.wiki.parseFilter(filter);
|
||||
expect(tree[0].operators[0].titleQuote).toBeUndefined();
|
||||
var serialized = $tw.utils.serializeFilterParseTree(tree);
|
||||
expect(serialized).toBe("[title[MyTitle]]");
|
||||
});
|
||||
|
||||
it("should round-trip multiple shorthand titles with different quotes", function () {
|
||||
var filter = "\"Title One\" 'Title Two' TitleThree";
|
||||
var tree = $tw.wiki.parseFilter(filter);
|
||||
expect(tree[0].operators[0].titleQuote).toBe("double");
|
||||
expect(tree[1].operators[0].titleQuote).toBe("single");
|
||||
expect(tree[2].operators[0].titleQuote).toBe("none");
|
||||
var serialized = $tw.utils.serializeFilterParseTree(tree);
|
||||
expect(serialized).toBe("\"Title One\" 'Title Two' TitleThree");
|
||||
});
|
||||
|
||||
it("should serialize shorthand title mixed with a regular run", function () {
|
||||
var filter = "MyTitle [tag[docs]]";
|
||||
var tree = $tw.wiki.parseFilter(filter);
|
||||
var serialized = $tw.utils.serializeFilterParseTree(tree);
|
||||
expect(serialized).toBe("MyTitle [tag[docs]]");
|
||||
});
|
||||
|
||||
// --- Formatting options ---
|
||||
|
||||
it("should wrap at maxRunsPerLine", function () {
|
||||
var filter = "[tag[a]] [tag[b]] [tag[c]] [tag[d]]";
|
||||
var tree = $tw.wiki.parseFilter(filter);
|
||||
var serialized = $tw.utils.serializeFilterParseTree(tree, {maxRunsPerLine: 2});
|
||||
expect(serialized).toBe("[tag[a]] [tag[b]]\n [tag[c]] [tag[d]]");
|
||||
});
|
||||
|
||||
it("should use custom indent string", function () {
|
||||
var filter = "[tag[a]] [tag[b]] [tag[c]]";
|
||||
var tree = $tw.wiki.parseFilter(filter);
|
||||
var serialized = $tw.utils.serializeFilterParseTree(tree, {maxRunsPerLine: 1, indent: "\t"});
|
||||
expect(serialized).toBe("[tag[a]]\n\t[tag[b]]\n\t[tag[c]]");
|
||||
});
|
||||
|
||||
it("should wrap at wrapAt column width", function () {
|
||||
var filter = "[tag[alpha]] [tag[beta]] [tag[gamma]]";
|
||||
var tree = $tw.wiki.parseFilter(filter);
|
||||
// "[tag[alpha]]" is 12 chars, " [tag[beta]]" would make 24, " [tag[gamma]]" would make 37
|
||||
// wrapAt:20 → wrap before [tag[beta]]
|
||||
var serialized = $tw.utils.serializeFilterParseTree(tree, {wrapAt: 20});
|
||||
expect(serialized).toBe("[tag[alpha]]\n [tag[beta]]\n [tag[gamma]]");
|
||||
});
|
||||
|
||||
it("should not wrap with default options", function () {
|
||||
var filter = "[tag[a]] [tag[b]] [tag[c]]";
|
||||
var tree = $tw.wiki.parseFilter(filter);
|
||||
var serialized = $tw.utils.serializeFilterParseTree(tree);
|
||||
expect(serialized).toBe("[tag[a]] [tag[b]] [tag[c]]");
|
||||
});
|
||||
});
|
||||
19
editions/tw5.com/tiddlers/releasenotes/5.5.0/#PRNUM.tid
Normal file
19
editions/tw5.com/tiddlers/releasenotes/5.5.0/#PRNUM.tid
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
title: $:/changenotes/5.5.0/#PRNUM
|
||||
description: Filter serialization: CST quote-style preservation and formatting options
|
||||
tags: $:/tags/ChangeNote
|
||||
release: 5.5.0
|
||||
change-type: enhancement
|
||||
change-category: developer
|
||||
github-links: https://github.com/TiddlyWiki/TiddlyWiki5/pull/#PRNUM
|
||||
github-contributors: linonetwo
|
||||
|
||||
Extends the filter serialization work introduced in v5.4.0 with two improvements:
|
||||
|
||||
* ''CST round-trip for shorthand title syntax'': The filter parser now preserves a `titleQuote` property (`"double"`, `"single"`, or `"none"`) on operators produced by shorthand title syntax (`"My Title"`, `'My Title'`, `MyTitle`). The serializer uses this metadata so that `$tw.utils.serializeFilterParseTree()` faithfully reproduces the original quoting style instead of normalising everything to `[title[...]]`.
|
||||
|
||||
* ''Formatting options'': `serializeFilterParseTree(tree, options)` now accepts:
|
||||
** `maxRunsPerLine` — insert a newline + indent after every N filter runs
|
||||
** `wrapAt` — wrap at approximately a given column width
|
||||
** `indent` — the indentation string used when wrapping (default `" "`)
|
||||
|
||||
These options lay the groundwork for a future WikiText linter and formatter.
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
caption: 5.5.0
|
||||
created: 20260310000000000
|
||||
modified: 20260310000000000
|
||||
tags: ReleaseNotes
|
||||
title: Release 5.5.0
|
||||
type: text/vnd.tiddlywiki
|
||||
description: Under development
|
||||
|
||||
\procedure release-introduction()
|
||||
Release v5.5.0.
|
||||
\end release-introduction
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
/*\
|
||||
title: $:/plugins/tiddlywiki/wikitext-serialize/rules/mvvdisplayinline.js
|
||||
type: application/javascript
|
||||
module-type: wikiruleserializer
|
||||
|
||||
Serializer for the mvvdisplayinline rule.
|
||||
|
||||
Variable display: ((varname)) or ((varname||separator))
|
||||
Filter display: (((filter))) or (((filter||separator)))
|
||||
|
||||
The default separator is ", " (comma space).
|
||||
|
||||
\*/
|
||||
|
||||
"use strict";
|
||||
|
||||
exports.name = "mvvdisplayinline";
|
||||
|
||||
exports.serialize = function(tree, serialize) {
|
||||
var filter = tree.attributes.text.filter;
|
||||
// Variable mode produces: [(varname)join[sep]]
|
||||
var varMatch = /^\[\(([^()]+)\)join\[([^\]]*)\]\]$/.exec(filter);
|
||||
if(varMatch) {
|
||||
var varName = varMatch[1];
|
||||
var sep = varMatch[2];
|
||||
if(sep === ", ") {
|
||||
return "((" + varName + "))";
|
||||
} else {
|
||||
return "((" + varName + "||" + sep + "))";
|
||||
}
|
||||
}
|
||||
// Filter mode produces: originalFilter +[join[sep]]
|
||||
var filterMatch = /^([\s\S]*) \+\[join\[([^\]]*)\]\]$/.exec(filter);
|
||||
if(filterMatch) {
|
||||
var innerFilter = filterMatch[1];
|
||||
var filterSep = filterMatch[2];
|
||||
if(filterSep === ", ") {
|
||||
return "(((" + innerFilter + ")))";
|
||||
} else {
|
||||
return "(((" + innerFilter + "||" + filterSep + ")))";
|
||||
}
|
||||
}
|
||||
// Fallback: should not occur in normal usage
|
||||
return "(((" + filter + ")))";
|
||||
};
|
||||
137
plugins/tiddlywiki/wikitext-serialize/utils/filter.js
Normal file
137
plugins/tiddlywiki/wikitext-serialize/utils/filter.js
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
/*\
|
||||
title: $:/plugins/tiddlywiki/wikitext-serialize/utils/filter.js
|
||||
type: application/javascript
|
||||
module-type: utils
|
||||
|
||||
Filter parse tree serialization utility functions.
|
||||
|
||||
Serializes the output of $tw.wiki.parseFilter() back into a filter string.
|
||||
|
||||
Why this fits in one file: the filter AST has a uniform, flat schema —
|
||||
every node follows the same shape (run → operators → operands), unlike
|
||||
the wikitext AST where each rule creates entirely different node structures.
|
||||
|
||||
The filter parser preserves CST (Concrete Syntax Tree) information in the
|
||||
`titleQuote` property on shorthand title operators so that the original
|
||||
quote style can be round-tripped:
|
||||
- `titleQuote: "double"` → "My Title"
|
||||
- `titleQuote: "single"` → 'My Title'
|
||||
- `titleQuote: "none"` → MyTitle (unquoted)
|
||||
- absent / undefined → [title[My Title]] (explicit bracket form)
|
||||
|
||||
\*/
|
||||
|
||||
"use strict";
|
||||
|
||||
/*
|
||||
Serialize a single filter operand back to its bracket string.
|
||||
*/
|
||||
const serializeOperand = (operand) => {
|
||||
if(operand.indirect) return `{${operand.text}}`;
|
||||
if(operand.variable) return `<${operand.text}>`;
|
||||
if(operand.multiValuedVariable) return `(${operand.text})`;
|
||||
return `[${operand.text}]`;
|
||||
};
|
||||
|
||||
/*
|
||||
Serialize a single filter operator (name + optional suffix + operands) to string.
|
||||
When the operator is a shorthand title (`titleQuote` present), emit the original
|
||||
quoting style instead of the canonical bracket form.
|
||||
*/
|
||||
const serializeOperator = (operator) => {
|
||||
// Shorthand title syntax: restore original quote style
|
||||
if(operator.titleQuote !== undefined && operator.operator === "title" && operator.operands.length === 1 && !operator.prefix) {
|
||||
const text = operator.operands[0].text;
|
||||
switch(operator.titleQuote) {
|
||||
case "double": return `"${text}"`;
|
||||
case "single": return `'${text}'`;
|
||||
case "none": return text;
|
||||
}
|
||||
}
|
||||
// Operator negation prefix ("!"), name, optional raw suffix
|
||||
let result = `${operator.prefix || ""}${operator.operator}`;
|
||||
if(operator.suffix) {
|
||||
result += `:${operator.suffix}`;
|
||||
}
|
||||
// Operands, comma-separated from the second one onwards
|
||||
operator.operands.forEach((operand, index) => {
|
||||
if(index > 0) result += ",";
|
||||
result += serializeOperand(operand);
|
||||
});
|
||||
return result;
|
||||
};
|
||||
|
||||
/*
|
||||
Serialize a filter parse tree (as returned by $tw.wiki.parseFilter()) back to a
|
||||
filter string.
|
||||
|
||||
Options:
|
||||
maxRunsPerLine {number} - if set, insert a newline + indent after every N runs
|
||||
(useful for formatting long filters). Default: unlimited.
|
||||
indent {string} - indentation string used when wrapping. Default: " ".
|
||||
wrapAt {number} - if set, wrap at approximately this column width by
|
||||
inserting a newline before the next run that would exceed
|
||||
it. Default: unlimited.
|
||||
*/
|
||||
exports.serializeFilterParseTree = function serializeFilterParseTree(tree, options) {
|
||||
if(!$tw.utils.isArray(tree)) return "";
|
||||
|
||||
options = options || {};
|
||||
const indent = options.indent !== undefined ? options.indent : " ";
|
||||
const maxRunsPerLine = options.maxRunsPerLine || 0;
|
||||
const wrapAt = options.wrapAt || 0;
|
||||
|
||||
const runs = tree.map((operation) => {
|
||||
// Reconstruct the run prefix: named (:filter, :reduce:flat…) or symbolic (+, -, ~, =, =>)
|
||||
let prefix = "";
|
||||
if(operation.namedPrefix) {
|
||||
prefix = `:${operation.namedPrefix}`;
|
||||
if(operation.suffixes) {
|
||||
operation.suffixes.forEach((subsuffix) => {
|
||||
prefix += `:${subsuffix.join(",")}`;
|
||||
});
|
||||
}
|
||||
} else if(operation.prefix) {
|
||||
prefix = operation.prefix;
|
||||
}
|
||||
|
||||
// Shorthand title operators are serialized without brackets
|
||||
const isTitleShorthand = operation.operators.length === 1 &&
|
||||
operation.operators[0].titleQuote !== undefined &&
|
||||
operation.operators[0].operator === "title" &&
|
||||
!operation.operators[0].prefix &&
|
||||
operation.operators[0].operands.length === 1;
|
||||
if(isTitleShorthand) {
|
||||
return prefix + serializeOperator(operation.operators[0]);
|
||||
}
|
||||
|
||||
const operatorsStr = operation.operators.map(serializeOperator).join("");
|
||||
return `${prefix}[${operatorsStr}]`;
|
||||
});
|
||||
|
||||
// Simple join if no wrapping requested
|
||||
if(!maxRunsPerLine && !wrapAt) {
|
||||
return runs.join(" ");
|
||||
}
|
||||
|
||||
// Apply wrapping
|
||||
let output = "";
|
||||
let lineLen = 0;
|
||||
let runsOnLine = 0;
|
||||
runs.forEach((run, index) => {
|
||||
const sep = index === 0 ? "" : " ";
|
||||
const candidate = sep + run;
|
||||
const needsWrap = (maxRunsPerLine && runsOnLine >= maxRunsPerLine) ||
|
||||
(wrapAt && lineLen + candidate.length > wrapAt && index > 0);
|
||||
if(needsWrap) {
|
||||
output += "\n" + indent + run;
|
||||
lineLen = indent.length + run.length;
|
||||
runsOnLine = 1;
|
||||
} else {
|
||||
output += candidate;
|
||||
lineLen += candidate.length;
|
||||
runsOnLine++;
|
||||
}
|
||||
});
|
||||
return output;
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue