UNPKG

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