Path: blob/master/src/packages/conat/core/patterns.ts
1453 views
import { isEqual } from "lodash";1import { getLogger } from "@cocalc/conat/client";2import { EventEmitter } from "events";34type Index = { [pattern: string]: Index | string };56const logger = getLogger("pattern");78export class Patterns<T> extends EventEmitter {9private patterns: { [pattern: string]: T } = {};10private index: Index = {};1112constructor() {13super();14this.setMaxListeners(1000);15}1617close = () => {18this.emit("closed");19this.patterns = {};20this.index = {};21};2223serialize = (fromT?: (x: T) => any) => {24let patterns: { [pattern: string]: any };25if (fromT != null) {26patterns = {};27for (const pattern in this.patterns) {28patterns[pattern] = fromT(this.patterns[pattern]);29}30} else {31patterns = this.patterns;32}3334return { patterns, index: this.index };35};3637deserialize = (38{ patterns, index }: { patterns: { [pattern: string]: any }; index: Index },39toT?: (x: any) => T,40) => {41if (toT != null) {42for (const pattern in patterns) {43patterns[pattern] = toT(patterns[pattern]); // make it of type T44}45}46this.patterns = patterns;47this.index = index;48this.emit("change");49};5051// mutate this by merging in data from p.52merge = (p: Patterns<T>) => {53for (const pattern in p.patterns) {54const t = p.patterns[pattern];55this.set(pattern, t);56}57this.emit("change");58};5960matches = (subject: string): string[] => {61return matchUsingIndex(this.index, subject.split("."));62};6364matchesTest = (subject: string): string[] => {65const a = this.matches(subject);66const b = this.matchNaive(subject);67a.sort();68b.sort();69if (!isEqual(a, b)) {70logger.debug("BUG in PATTERN MATCHING!!!", {71subject,72a,73b,74index: this.index,75patterns: Object.keys(this.patterns),76});77}78return b;79};8081matchNaive = (subject: string): string[] => {82const v: string[] = [];83for (const pattern in this.patterns) {84if (matchesPattern(pattern, subject)) {85v.push(pattern);86}87}88return v;89};9091get = (pattern: string): T | undefined => {92return this.patterns[pattern];93};9495set = (pattern: string, t: T) => {96this.patterns[pattern] = t;97setIndex(this.index, pattern.split("."), pattern);98this.emit("change");99};100101delete = (pattern: string) => {102delete this.patterns[pattern];103deleteIndex(this.index, pattern.split("."));104};105}106107function setIndex(index: Index, segments: string[], pattern) {108if (segments.length == 0) {109index[""] = pattern;110return;111}112if (segments[0] == ">") {113// there can't be anything after it114index[">"] = pattern;115return;116}117const v = index[segments[0]];118if (v === undefined) {119const idx: Index = {};120setIndex(idx, segments.slice(1), pattern);121index[segments[0]] = idx;122return;123}124if (typeof v == "string") {125// already set126return;127}128setIndex(v, segments.slice(1), pattern);129}130131function deleteIndex(index: Index, segments: string[]) {132const ind = index[segments[0]];133if (ind === undefined) {134return;135}136if (typeof ind != "string") {137deleteIndex(ind, segments.slice(1));138// if there is anything still stored in ind139// besides ind[''], we do NOT delete it.140for (const key in ind) {141if (key != "") {142return;143}144}145}146delete index[segments[0]];147}148149// todo deal with >150function matchUsingIndex(index: Index, segments: string[]): string[] {151if (segments.length == 0) {152const p = index[""];153if (p === undefined) {154return [];155} else if (typeof p === "string") {156return [p];157} else {158throw Error("bug");159}160}161const matches: string[] = [];162const subject = segments[0];163for (const pattern of ["*", ">", subject]) {164if (index[pattern] !== undefined) {165const p = index[pattern];166if (typeof p == "string") {167// end of this pattern -- matches if segments also168// stops *or* this pattern is >169if (segments.length == 1) {170matches.push(p);171} else if (pattern == ">") {172matches.push(p);173}174} else {175for (const s of matchUsingIndex(p, segments.slice(1))) {176matches.push(s);177}178}179}180}181return matches;182}183184export function matchesSegment(pattern, subject): boolean {185if (pattern == "*" || pattern == ">") {186return true;187}188return pattern == subject;189}190191export function matchesPattern(pattern, subject): boolean {192const subParts = subject.split(".");193const patParts = pattern.split(".");194let i = 0,195j = 0;196while (i < subParts.length && j < patParts.length) {197if (patParts[j] === ">") return true;198if (patParts[j] !== "*" && patParts[j] !== subParts[i]) return false;199i++;200j++;201}202203return i === subParts.length && j === patParts.length;204}205206207