184 lines
4.1 KiB
JavaScript
184 lines
4.1 KiB
JavaScript
"use strict";
|
|
|
|
function traverseDoc(doc, onEnter, onExit, shouldTraverseConditionalGroups) {
|
|
function traverseDocRec(doc) {
|
|
let shouldRecurse = true;
|
|
if (onEnter) {
|
|
if (onEnter(doc) === false) {
|
|
shouldRecurse = false;
|
|
}
|
|
}
|
|
|
|
if (shouldRecurse) {
|
|
if (doc.type === "concat" || doc.type === "fill") {
|
|
for (let i = 0; i < doc.parts.length; i++) {
|
|
traverseDocRec(doc.parts[i]);
|
|
}
|
|
} else if (doc.type === "if-break") {
|
|
if (doc.breakContents) {
|
|
traverseDocRec(doc.breakContents);
|
|
}
|
|
if (doc.flatContents) {
|
|
traverseDocRec(doc.flatContents);
|
|
}
|
|
} else if (doc.type === "group" && doc.expandedStates) {
|
|
if (shouldTraverseConditionalGroups) {
|
|
doc.expandedStates.forEach(traverseDocRec);
|
|
} else {
|
|
traverseDocRec(doc.contents);
|
|
}
|
|
} else if (doc.contents) {
|
|
traverseDocRec(doc.contents);
|
|
}
|
|
}
|
|
|
|
if (onExit) {
|
|
onExit(doc);
|
|
}
|
|
}
|
|
|
|
traverseDocRec(doc);
|
|
}
|
|
|
|
function mapDoc(doc, func) {
|
|
doc = func(doc);
|
|
|
|
if (doc.type === "concat" || doc.type === "fill") {
|
|
return Object.assign({}, doc, {
|
|
parts: doc.parts.map(d => mapDoc(d, func))
|
|
});
|
|
} else if (doc.type === "if-break") {
|
|
return Object.assign({}, doc, {
|
|
breakContents: doc.breakContents && mapDoc(doc.breakContents, func),
|
|
flatContents: doc.flatContents && mapDoc(doc.flatContents, func)
|
|
});
|
|
} else if (doc.contents) {
|
|
return Object.assign({}, doc, { contents: mapDoc(doc.contents, func) });
|
|
}
|
|
return doc;
|
|
}
|
|
|
|
function findInDoc(doc, fn, defaultValue) {
|
|
let result = defaultValue;
|
|
let hasStopped = false;
|
|
traverseDoc(doc, doc => {
|
|
const maybeResult = fn(doc);
|
|
if (maybeResult !== undefined) {
|
|
hasStopped = true;
|
|
result = maybeResult;
|
|
}
|
|
if (hasStopped) {
|
|
return false;
|
|
}
|
|
});
|
|
return result;
|
|
}
|
|
|
|
function isEmpty(n) {
|
|
return typeof n === "string" && n.length === 0;
|
|
}
|
|
|
|
function isLineNext(doc) {
|
|
return findInDoc(
|
|
doc,
|
|
doc => {
|
|
if (typeof doc === "string") {
|
|
return false;
|
|
}
|
|
if (doc.type === "line") {
|
|
return true;
|
|
}
|
|
},
|
|
false
|
|
);
|
|
}
|
|
|
|
function willBreak(doc) {
|
|
return findInDoc(
|
|
doc,
|
|
doc => {
|
|
if (doc.type === "group" && doc.break) {
|
|
return true;
|
|
}
|
|
if (doc.type === "line" && doc.hard) {
|
|
return true;
|
|
}
|
|
if (doc.type === "break-parent") {
|
|
return true;
|
|
}
|
|
},
|
|
false
|
|
);
|
|
}
|
|
|
|
function breakParentGroup(groupStack) {
|
|
if (groupStack.length > 0) {
|
|
const parentGroup = groupStack[groupStack.length - 1];
|
|
// Breaks are not propagated through conditional groups because
|
|
// the user is expected to manually handle what breaks.
|
|
if (!parentGroup.expandedStates) {
|
|
parentGroup.break = true;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function propagateBreaks(doc) {
|
|
const alreadyVisited = new Map();
|
|
const groupStack = [];
|
|
traverseDoc(
|
|
doc,
|
|
doc => {
|
|
if (doc.type === "break-parent") {
|
|
breakParentGroup(groupStack);
|
|
}
|
|
if (doc.type === "group") {
|
|
groupStack.push(doc);
|
|
if (alreadyVisited.has(doc)) {
|
|
return false;
|
|
}
|
|
alreadyVisited.set(doc, true);
|
|
}
|
|
},
|
|
doc => {
|
|
if (doc.type === "group") {
|
|
const group = groupStack.pop();
|
|
if (group.break) {
|
|
breakParentGroup(groupStack);
|
|
}
|
|
}
|
|
},
|
|
/* shouldTraverseConditionalGroups */ true
|
|
);
|
|
}
|
|
|
|
function removeLines(doc) {
|
|
// Force this doc into flat mode by statically converting all
|
|
// lines into spaces (or soft lines into nothing). Hard lines
|
|
// should still output because there's too great of a chance
|
|
// of breaking existing assumptions otherwise.
|
|
return mapDoc(doc, d => {
|
|
if (d.type === "line" && !d.hard) {
|
|
return d.soft ? "" : " ";
|
|
} else if (d.type === "if-break") {
|
|
return d.flatContents || "";
|
|
}
|
|
return d;
|
|
});
|
|
}
|
|
|
|
function rawText(node) {
|
|
return node.extra ? node.extra.raw : node.raw;
|
|
}
|
|
|
|
module.exports = {
|
|
isEmpty,
|
|
willBreak,
|
|
isLineNext,
|
|
traverseDoc,
|
|
mapDoc,
|
|
propagateBreaks,
|
|
removeLines,
|
|
rawText
|
|
};
|