Sindbad~EG File Manager
'use strict';
const Hoek = require('@hapi/hoek');
const internals = {};
exports = module.exports = internals.Segment = function () {
this._edge = null; // { segment, record }
this._fulls = null; // { path: { segment, record }
this._literals = null; // { literal: { segment, <node> } }
this._param = null; // <node>
this._mixed = null; // [{ segment, <node> }]
this._wildcard = null; // { segment, record }
};
internals.Segment.prototype.add = function (segments, record) {
/*
{ literal: 'x' } -> x
{ empty: false } -> {p}
{ wildcard: true } -> {p*}
{ mixed: /regex/ } -> a{p}b
*/
const current = segments[0];
const remaining = segments.slice(1);
const isEdge = !remaining.length;
const literals = [];
let isLiteral = true;
for (let i = 0; i < segments.length && isLiteral; ++i) {
isLiteral = segments[i].literal !== undefined;
literals.push(segments[i].literal);
}
if (isLiteral) {
this._fulls = this._fulls ?? new Map();
let literal = '/' + literals.join('/');
if (!record.settings.isCaseSensitive) {
literal = literal.toLowerCase();
}
Hoek.assert(!this._fulls.has(literal), 'New route', record.path, 'conflicts with existing', this._fulls.get(literal)?.record.path);
this._fulls.set(literal, { segment: current, record });
}
else if (current.literal !== undefined) { // Can be empty string
// Literal
this._literals = this._literals ?? new Map();
const currentLiteral = record.settings.isCaseSensitive ? current.literal : current.literal.toLowerCase();
if (!this._literals.has(currentLiteral)) {
this._literals.set(currentLiteral, new internals.Segment());
}
this._literals.get(currentLiteral).add(remaining, record);
}
else if (current.wildcard) {
// Wildcard
Hoek.assert(!this._wildcard, 'New route', record.path, 'conflicts with existing', this._wildcard?.record.path);
Hoek.assert(!this._param || !this._param._wildcard, 'New route', record.path, 'conflicts with existing', this._param?._wildcard?.record.path);
this._wildcard = { segment: current, record };
}
else if (current.mixed) {
// Mixed
this._mixed = this._mixed ?? [];
let mixed = this._mixedLookup(current);
if (!mixed) {
mixed = { segment: current, node: new internals.Segment() };
this._mixed.push(mixed);
this._mixed.sort(internals.mixed);
}
if (isEdge) {
Hoek.assert(!mixed.node._edge, 'New route', record.path, 'conflicts with existing', mixed.node._edge?.record.path);
mixed.node._edge = { segment: current, record };
}
else {
mixed.node.add(remaining, record);
}
}
else {
// Parameter
this._param = this._param ?? new internals.Segment();
if (isEdge) {
Hoek.assert(!this._param._edge, 'New route', record.path, 'conflicts with existing', this._param._edge?.record.path);
this._param._edge = { segment: current, record };
}
else {
Hoek.assert(!this._wildcard || !remaining[0].wildcard, 'New route', record.path, 'conflicts with existing', this._wildcard?.record.path);
this._param.add(remaining, record);
}
}
};
internals.Segment.prototype._mixedLookup = function (segment) {
for (let i = 0; i < this._mixed.length; ++i) {
if (internals.mixed({ segment }, this._mixed[i]) === 0) {
return this._mixed[i];
}
}
return null;
};
internals.mixed = function (a, b) {
const aFirst = -1;
const bFirst = 1;
const as = a.segment;
const bs = b.segment;
if (as.length !== bs.length) {
return as.length > bs.length ? aFirst : bFirst;
}
if (as.first !== bs.first) {
return as.first ? bFirst : aFirst;
}
for (let i = 0; i < as.segments.length; ++i) {
const am = as.segments[i];
const bm = bs.segments[i];
if (am === bm) {
continue;
}
if (am.length === bm.length) {
return am > bm ? bFirst : aFirst;
}
return am.length < bm.length ? bFirst : aFirst;
}
return 0;
};
internals.Segment.prototype.lookup = function (path, segments, options) {
let match = null;
// Literal edge
if (this._fulls) {
match = this._fulls.get(options.isCaseSensitive ? path : path.toLowerCase());
if (match) {
return { record: match.record, array: [] };
}
}
// Literal node
const current = segments[0];
const nextPath = path.slice(current.length + 1);
const remainder = segments.length > 1 ? segments.slice(1) : null;
if (this._literals) {
const literal = options.isCaseSensitive ? current : current.toLowerCase();
match = this._literals.get(literal);
if (match) {
const record = internals.deeper(match, nextPath, remainder, [], options);
if (record) {
return record;
}
}
}
// Mixed
if (this._mixed) {
for (let i = 0; i < this._mixed.length; ++i) {
match = this._mixed[i];
const params = current.match(match.segment.mixed);
if (params) {
const array = [];
for (let j = 1; j < params.length; ++j) {
array.push(params[j]);
}
const record = internals.deeper(match.node, nextPath, remainder, array, options);
if (record) {
return record;
}
}
}
}
// Param
if (this._param) {
if (current || this._param._edge?.segment.empty) {
const record = internals.deeper(this._param, nextPath, remainder, [current], options);
if (record) {
return record;
}
}
}
// Wildcard
if (this._wildcard) {
return { record: this._wildcard.record, array: [path.slice(1)] };
}
return null;
};
internals.deeper = function (match, path, segments, array, options) {
if (!segments) {
if (match._edge) {
return { record: match._edge.record, array };
}
if (match._wildcard) {
return { record: match._wildcard.record, array };
}
}
else {
const result = match.lookup(path, segments, options);
if (result) {
return { record: result.record, array: array.concat(result.array) };
}
}
return null;
};
Sindbad File Manager Version 1.0, Coded By Sindbad EG ~ The Terrorists