Path: blob/master/src/packages/conat/core/patterns.ts
1710 views
import { isEqual } from "lodash";1import { getLogger } from "@cocalc/conat/client";2import { EventEmitter } from "events";3import { hash_string } from "@cocalc/util/misc";45type Index = { [pattern: string]: Index | string };67const logger = getLogger("pattern");89export class Patterns<T> extends EventEmitter {10private patterns: { [pattern: string]: T } = {};11private index: Index = {};1213constructor() {14super();15this.setMaxListeners(1000);16}1718close = () => {19this.emit("closed");20this.patterns = {};21this.index = {};22};2324hash = (hashT: (x: T) => number): number => {25let h = 0;26for (const pattern in this.patterns) {27h += hash_string(pattern) + hashT(this.patterns[pattern]);28}29return h;30};3132serialize = (fromT?: (x: T) => any) => {33let patterns: { [pattern: string]: any };34if (fromT != null) {35patterns = {};36for (const pattern in this.patterns) {37patterns[pattern] = fromT(this.patterns[pattern]);38}39} else {40patterns = this.patterns;41}4243return { patterns, index: this.index };44};4546deserialize = (47{ patterns, index }: { patterns: { [pattern: string]: any }; index: Index },48toT?: (x: any) => T,49) => {50if (toT != null) {51for (const pattern in patterns) {52patterns[pattern] = toT(patterns[pattern]); // make it of type T53}54}55this.patterns = patterns;56this.index = index;57this.emit("change");58};5960// mutate this by merging in data from p.61merge = (p: Patterns<T>) => {62for (const pattern in p.patterns) {63const t = p.patterns[pattern];64this.set(pattern, t);65}66this.emit("change");67};6869matches = (subject: string): string[] => {70return matchUsingIndex(this.index, subject.split("."));71};7273// return true if there is at least one match74hasMatch = (subject: string): boolean => {75return matchUsingIndex(this.index, subject.split("."), true).length > 0;76};7778hasPattern = (pattern: string): boolean => {79return this.patterns[pattern] !== undefined;80};8182matchesTest = (subject: string): string[] => {83const a = this.matches(subject);84const b = this.matchNaive(subject);85a.sort();86b.sort();87if (!isEqual(a, b)) {88logger.debug("BUG in PATTERN MATCHING!!!", {89subject,90a,91b,92index: this.index,93patterns: Object.keys(this.patterns),94});95}96return b;97};9899matchNaive = (subject: string): string[] => {100const v: string[] = [];101for (const pattern in this.patterns) {102if (matchesPattern(pattern, subject)) {103v.push(pattern);104}105}106return v;107};108109get = (pattern: string): T | undefined => {110return this.patterns[pattern];111};112113set = (pattern: string, t: T) => {114this.patterns[pattern] = t;115setIndex(this.index, pattern.split("."), pattern);116this.emit("change");117};118119delete = (pattern: string) => {120delete this.patterns[pattern];121deleteIndex(this.index, pattern.split("."));122};123}124125function setIndex(index: Index, segments: string[], pattern) {126if (segments.length == 0) {127index[""] = pattern;128return;129}130if (segments[0] == ">") {131// there can't be anything after it132index[">"] = pattern;133return;134}135const v = index[segments[0]];136if (v === undefined) {137const idx: Index = {};138setIndex(idx, segments.slice(1), pattern);139index[segments[0]] = idx;140return;141}142if (typeof v == "string") {143// already set144return;145}146setIndex(v, segments.slice(1), pattern);147}148149function deleteIndex(index: Index, segments: string[]) {150const ind = index[segments[0]];151if (ind === undefined) {152return;153}154if (typeof ind != "string") {155deleteIndex(ind, segments.slice(1));156// if there is anything still stored in ind157// besides ind[''], we do NOT delete it.158for (const key in ind) {159if (key != "") {160return;161}162}163}164delete index[segments[0]];165}166167// todo deal with >168function matchUsingIndex(169index: Index,170segments: string[],171atMostOne = false,172): string[] {173if (segments.length == 0) {174const p = index[""];175if (p === undefined) {176return [];177} else if (typeof p === "string") {178return [p];179} else {180throw Error("bug");181}182}183const matches: string[] = [];184const subject = segments[0];185for (const pattern of ["*", ">", subject]) {186if (index[pattern] !== undefined) {187const p = index[pattern];188if (typeof p == "string") {189// end of this pattern -- matches if segments also190// stops *or* this pattern is >191if (segments.length == 1) {192matches.push(p);193if (atMostOne) {194return matches;195}196} else if (pattern == ">") {197matches.push(p);198if (atMostOne) {199return matches;200}201}202} else {203for (const s of matchUsingIndex(p, segments.slice(1), atMostOne)) {204matches.push(s);205if (atMostOne) {206return matches;207}208}209}210}211}212return matches;213}214215export function matchesSegment(pattern, subject): boolean {216if (pattern == "*" || pattern == ">") {217return true;218}219return pattern == subject;220}221222export function matchesPattern(pattern, subject): boolean {223const subParts = subject.split(".");224const patParts = pattern.split(".");225let i = 0,226j = 0;227while (i < subParts.length && j < patParts.length) {228if (patParts[j] === ">") return true;229if (patParts[j] !== "*" && patParts[j] !== subParts[i]) return false;230i++;231j++;232}233234return i === subParts.length && j === patParts.length;235}236237238