diffusion
Version:
Diffusion JavaScript client
240 lines (239 loc) • 8.58 kB
JavaScript
"use strict";
/**
* @module Topics
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.parseSelector = void 0;
// topic selector API
// internal topic classes
var errors_1 = require("./../../errors/errors");
var abstract_topic_selector_1 = require("./../topics/abstract-topic-selector");
var full_path_selector_1 = require("./../topics/full-path-selector");
var path_selector_1 = require("./../topics/path-selector");
var selector_set_1 = require("./../topics/selector-set");
var split_path_selector_1 = require("./../topics/split-path-selector");
var utils = require("./../topics/topic-path-utils");
var memoize_1 = require("./../util/memoize");
var string_1 = require("./../util/string");
var topic_selector_1 = require("../../selectors/topic-selector");
var DQ = utils.DescendantQualifier;
/**
* Parse a selector expression into a specific TopicSelector implementation.
* @param expression the selector's string expression
* @return a new topic selector
* @throws an {@link IllegalArgumentError} if the expression is empty or invalid
*/
function parseSelector(expression) {
var args = [];
for (var _i = 1; _i < arguments.length; _i++) {
args[_i - 1] = arguments[_i];
}
if (!expression) {
throw new errors_1.IllegalArgumentError('Empty topic selector expression');
}
if (args.length > 0) {
expression = [expression].concat(args);
}
if (expression instanceof Array) {
var actual = expression.map(function (s) { return (s instanceof abstract_topic_selector_1.AbstractTopicSelector ? s : parseSelector(s)); });
return new selector_set_1.SelectorSet(actual);
}
if (typeof expression === 'string') {
var components = getComponents(expression);
if (isTopicPath(components)) {
return new path_selector_1.PathSelector(components);
}
switch (components.type) {
case topic_selector_1.Type.SPLIT_PATH_PATTERN:
return new split_path_selector_1.SplitPathSelector(components);
case topic_selector_1.Type.FULL_PATH_PATTERN:
return new full_path_selector_1.FullPathSelector(components);
case topic_selector_1.Type.SELECTOR_SET:
var parts = string_1.split(components.remainder, selector_set_1.DELIMITER);
var selectors = parts.map(function (e) { return parseSelector(e); });
return new selector_set_1.SelectorSet(selectors);
}
}
if (expression instanceof abstract_topic_selector_1.AbstractTopicSelector) {
return expression;
}
throw new errors_1.IllegalArgumentError('Topic selector expression must be a string or array');
}
/**
* Extract a component group from a single expression
* @param expression the selector expression
* @returns an object containing parsed properties
*/
function getComponents(expression) {
var type = getType(expression);
// treat expressions as Path selectors by default
if (type === null) {
expression = topic_selector_1.Prefix.PATH + expression;
type = topic_selector_1.Type.PATH;
}
var remainder = expression.substring(1);
var qualifier;
var base;
if (type === topic_selector_1.Type.PATH) {
base = remainder;
qualifier = DQ.MATCH;
}
else if (remainder[remainder.length - 1] === '/') {
if (remainder[remainder.length - 2] === '/') {
qualifier = DQ.MATCH_AND_DESCENDANTS;
base = utils.canonicalise(remainder.substring(0, remainder.length - 2));
}
else {
qualifier = DQ.DESCENDANTS_OF_MATCH;
base = utils.canonicalise(remainder.substring(0, remainder.length - 1));
}
}
else {
base = utils.canonicalise(remainder);
qualifier = DQ.MATCH;
}
var prefix = (type === topic_selector_1.Type.PATH) ? utils.canonicalise(remainder) : extractPrefixFromRegex(base);
return {
type: type,
base: base,
prefix: prefix,
remainder: remainder,
qualifier: qualifier,
expression: type + remainder
};
}
/**
* Determine if a component group can be treated as a topic path.
*
* @param c the component group
* @returns if the components represent a topic path selector
*/
function isTopicPath(c) {
if (c.type === topic_selector_1.Type.PATH) {
return true;
}
if (c.type === topic_selector_1.Type.SELECTOR_SET || c.prefix === '') {
return false;
}
return c.prefix === c.base;
}
/**
* Determine the Topic Selector type of a given expression.
*
* @param expression the selector expression
* @returns the topic selector type, or null
* @throws an {@link IllegalArgumentError} if the expression is invalid
*/
function getType(expression) {
switch (expression[0]) {
case topic_selector_1.Prefix.PATH:
return topic_selector_1.Type.PATH;
case topic_selector_1.Prefix.SPLIT_PATH_PATTERN:
return topic_selector_1.Type.SPLIT_PATH_PATTERN;
case topic_selector_1.Prefix.FULL_PATH_PATTERN:
return topic_selector_1.Type.FULL_PATH_PATTERN;
case topic_selector_1.Prefix.SELECTOR_SET:
return topic_selector_1.Type.SELECTOR_SET;
case '$':
case '%':
case '&':
case '<':
// reserved character
throw new errors_1.IllegalArgumentError('Invalid expression type: ' + expression);
default:
// default is to treat null as a path
return null;
}
}
/**
* Invalid characters for a topic path to contain
*/
var metaChars = ['*', '.', '+', '?', '^', '$', '{', '}', '(', ')', '[', ']', '\\', '|'];
/**
* Validate characters in a normal parsing mode
*
* @param c the character to check
* @return `true` if the character is a normal path character
*/
function normal(c) {
return metaChars.indexOf(c) === -1;
}
/**
* Validated characters within a quoted block (\Q \E)
*
* @return `true`
*/
function quoted() {
return true;
}
/**
* Extract the largest possible topic path prefix from a string that may
* contain a regular expression.
*
* @param {String} remainder - The remainder (i.e expression minus prefix)
* @returns {String} The maximum topic path that could be extracted
*/ // eslint-disable-next-line complexity
function extractPrefixFromRegex(remainder) {
var buffer = [];
var composite = [];
var validator = normal;
for (var i = 0, l = remainder.length; i < l; ++i) {
var char = remainder[i];
// identify a metachar and transition to an appropriate parse mode.
// we can then safely skip over the '\' char & following metachar.
if (char === '\\' && i < l - 1) {
var next = remainder[i + 1];
if (validator === normal) {
i++;
if (next === 'Q') {
validator = quoted;
}
else if ((next < 'a' || next > 'z') && (next < 'A' || next > 'Z')) {
// an escaped alphabetic character can be assumed to be a meta construct
buffer.push(next);
}
else {
// an escaped metachar indicates the end of the run
buffer.length = 0;
break;
}
}
else if (next === 'E') {
i++;
validator = normal;
}
else {
buffer.push(char);
}
}
else if (char === utils.PATH_SEPARATOR) {
composite.push(buffer.join(''));
buffer.length = 0;
buffer.push(char);
}
else if (validator(char)) {
buffer.push(char);
}
else {
buffer.length = 0;
break;
}
}
composite.push(buffer.join(''));
return utils.canonicalise(composite.join(''));
}
/**
* A matcher function to memoize arguments for the {@link parseSelector} function
*
* @param args the arguments
* @return a JSON string of the arguments
*/
function memoizeMatcher(args) {
return JSON.stringify(args);
}
var disableTopicCache = process && process.env && (process.env.DISABLE_TOPIC_CACHE === 'true');
/**
* Wrap the parse function to cache results
*/
var cachedParse = disableTopicCache ? parseSelector : memoize_1.memoize(parseSelector, {}, memoizeMatcher);
exports.parseSelector = cachedParse;