@mdfriday/foundry
Version:
The core engine of MDFriday. Convert Markdown and shortcodes into fully themed static sites – Hugo-style, powered by TypeScript.
363 lines • 11 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.NodeShiftTreeWalker = exports.NodeShiftTree = void 0;
const radix_1 = require("../radix");
const dimensions_1 = require("./dimensions");
const support_1 = require("./support");
// NodeShiftTree is the root of a tree that can be shaped using the Shape method.
// Note that multiple shapes of the same tree is meant to be used concurrently,
// so use the applicable locking when needed.
class NodeShiftTree {
constructor(cfg) {
if (!cfg.shifter) {
throw new Error("Shifter is required");
}
this.shifter = cfg.shifter;
this.tree = new radix_1.Tree();
this.dims = [0]; // Initialize with default dimension
// Simple mutex implementation
let locked = false;
let readLocks = 0;
this.mu = {
lock: () => {
while (locked || readLocks > 0) {
// Busy wait
}
locked = true;
},
unlock: () => {
locked = false;
},
rLock: () => {
while (locked) {
// Busy wait
}
readLocks++;
},
rUnlock: () => {
readLocks--;
},
tryLock: () => {
if (locked || readLocks > 0) {
return false;
}
locked = true;
return true;
}
};
}
static new(cfg) {
return new NodeShiftTree(cfg);
}
delete(key) {
this.deleteInternal(key);
}
async deleteAll(key) {
await this.tree.walkPrefix(key, (k, value) => {
const [v, ok] = this.tree.delete(k);
if (ok) {
// Note: stale.MarkStale(v) is ignored as per requirements
}
return Promise.resolve(false);
});
}
async deletePrefix(prefix) {
let count = 0;
const keys = [];
await this.tree.walkPrefix(prefix, (key, value) => {
keys.push(key);
return Promise.resolve(false);
});
for (const key of keys) {
if (this.deleteInternal(key)) {
count++;
}
}
return count;
}
deleteInternal(key) {
let wasDeleted = false;
const [v, ok] = this.tree.get(key);
if (ok) {
const [deleted, isEmpty] = this.shifter.delete(v, this.dims);
wasDeleted = deleted;
if (isEmpty) {
this.tree.delete(key);
}
}
return wasDeleted;
}
async deletePrefixAll(prefix) {
let count = 0;
await this.tree.walkPrefix(prefix, (key, value) => {
const [v, ok] = this.tree.delete(key);
if (ok) {
// Note: stale.MarkStale(v) is ignored as per requirements
count++;
}
return Promise.resolve(false);
});
return count;
}
// Increment the value of dimension d by 1.
increment(d) {
return this.shape(d, this.dims[d] + 1);
}
insertIntoCurrentDimension(s, v) {
s = (0, support_1.mustValidateKey)((0, support_1.cleanKey)(s));
const [vv, ok] = this.tree.get(s);
if (ok) {
v = this.shifter.insertInto(vv, v, this.dims);
}
this.tree.insert(s, v);
return [v, true];
}
insertIntoValuesDimension(s, v) {
s = (0, support_1.mustValidateKey)((0, support_1.cleanKey)(s));
const [vv, ok] = this.tree.get(s);
if (ok) {
v = this.shifter.insert(vv, v);
}
this.tree.insert(s, v);
return [v, true];
}
insertRawWithLock(s, v) {
this.mu.lock();
try {
return this.tree.insert(s, v);
}
finally {
this.mu.unlock();
}
}
insertWithLock(s, v) {
this.mu.lock();
try {
return this.insertIntoValuesDimension(s, v);
}
finally {
this.mu.unlock();
}
}
len() {
return this.tree.len();
}
canLock() {
const ok = this.mu.tryLock();
if (ok) {
this.mu.unlock();
}
return ok;
}
// Lock locks the data store for read or read/write access until commit is invoked.
// Note that Root is not thread-safe outside of this transaction construct.
lock(writable) {
if (writable) {
this.mu.lock();
}
else {
this.mu.rLock();
}
return () => {
if (writable) {
this.mu.unlock();
}
else {
this.mu.rUnlock();
}
};
}
// LongestPrefix finds the longest prefix of s that exists in the tree that also matches the predicate (if set).
// Set exact to true to only match exact in the current dimension (e.g. language).
longestPrefix(s, exact, predicate) {
let currentPath = s;
while (true) {
const [longestPrefix, v, found] = this.tree.longestPrefix(currentPath);
if (found) {
const [t, ok] = this.shift(v, exact);
if (ok && (!predicate || predicate(t))) {
return [longestPrefix, t];
}
}
if (currentPath === '' || currentPath === '/') {
const zero = undefined;
return ['', zero];
}
// Walk up to find a node in the correct dimension.
const lastSlash = currentPath.lastIndexOf('/');
if (lastSlash === 0) {
currentPath = '';
}
else if (lastSlash > 0) {
currentPath = currentPath.substring(0, lastSlash);
}
else {
break;
}
}
const zero = undefined;
return ['', zero];
}
// LongestPrefixAll returns the longest prefix considering all tree dimensions.
longestPrefixAll(s) {
const [key, , found] = this.tree.longestPrefix(s);
return [key, found];
}
getRaw(s) {
const [v, ok] = this.tree.get(s);
if (!ok) {
const zero = undefined;
return [zero, false];
}
return [v, true];
}
async walkPrefixRaw(prefix, walker) {
await this.tree.walkPrefix(prefix, (key, value) => {
return walker(key, value);
});
}
// Shape the tree for dimension d to value v.
shape(d, v) {
const x = this.clone();
x.dims[d] = v;
return x;
}
toString() {
return `Root{${this.dims}}`;
}
get(s) {
const [t] = this.getInternal(s);
return t;
}
forEachInDimension(s, d, f) {
s = (0, support_1.cleanKey)(s);
const [v, ok] = this.tree.get(s);
if (!ok) {
return;
}
this.shifter.forEachInDimension(v, d, f);
}
has(s) {
const [, ok] = this.getInternal(s);
return ok;
}
clone() {
const cloned = Object.create(NodeShiftTree.prototype);
Object.assign(cloned, this);
cloned.dims = [...this.dims];
return cloned;
}
shift(t, exact) {
const [shifted, ok] = this.shifter.shift(t, this.dims, exact);
return [shifted, ok];
}
getInternal(s) {
s = (0, support_1.cleanKey)(s);
const [v, ok] = this.tree.get(s);
if (!ok) {
const zero = undefined;
return [zero, false];
}
const [t, success] = this.shift(v, true);
return [t, success];
}
}
exports.NodeShiftTree = NodeShiftTree;
class NodeShiftTreeWalker {
constructor(config) {
// Local state.
this.skipPrefixes = [];
if (!config.tree) {
throw new Error("Tree is required");
}
this.tree = config.tree;
this.handle = config.handle;
this.prefix = config.prefix || '';
this.lockType = config.lockType || support_1.LockType.LockTypeNone;
this.noShift = config.noShift || false;
this.exact = config.exact || false;
this.debug = config.debug || false;
this.walkContext = config.walkContext;
}
// Extend returns a new NodeShiftTreeWalker with the same configuration as the
// and the same WalkContext as the original.
// Any local state is reset.
extend() {
const extended = new NodeShiftTreeWalker({
tree: this.tree,
handle: this.handle,
prefix: this.prefix,
lockType: this.lockType,
noShift: this.noShift,
exact: this.exact,
debug: this.debug,
walkContext: this.walkContext
});
extended.resetLocalState();
return extended;
}
// SkipPrefix adds a prefix to be skipped in the walk.
skipPrefix(...prefixes) {
this.skipPrefixes.push(...prefixes);
}
// ShouldSkip returns whether the given key should be skipped in the walk.
shouldSkip(s) {
for (const prefix of this.skipPrefixes) {
if (s.startsWith(prefix)) {
return true;
}
}
return false;
}
async walk() {
this.resetLocalState();
let commit = null;
if (this.lockType > support_1.LockType.LockTypeNone) {
commit = this.tree.lock(this.lockType === support_1.LockType.LockTypeWrite);
}
try {
let err = null;
const fnMain = async (s, v) => {
if (this.shouldSkip(s)) {
return false;
}
const [t, ok, exact] = this.toT(this.tree, v);
if (!ok) {
return false;
}
const [terminate, error] = await this.handle(s, t, exact);
if (terminate || error) {
err = error;
return true;
}
return false;
};
if (this.prefix !== '') {
await this.tree.tree.walkPrefix(this.prefix, fnMain);
}
else {
await this.tree.tree.walk(fnMain);
}
return err;
}
finally {
if (commit) {
commit();
}
}
}
resetLocalState() {
this.skipPrefixes = [];
}
toT(tree, v) {
if (this.noShift) {
return [v, true, dimensions_1.DimensionFlag.DimensionNone];
}
else {
const [t, ok, exact] = tree.shifter.shift(v, tree.dims, this.exact);
return [t, ok, exact];
}
}
}
exports.NodeShiftTreeWalker = NodeShiftTreeWalker;
//# sourceMappingURL=nodeshifttree.js.map