lib/irc: add case-mapping primitives

irc.CaseMapping contains the basic canonicalization functions for
the three supported case-mappings. irc.CaseMapMap is a Map-like
class that supports case-mapped keys.
This commit is contained in:
Simon Ser 2021-05-27 15:13:32 +02:00
parent d880b23d32
commit 3110a9e2df

View File

@ -320,6 +320,7 @@ export function parseCTCP(msg) {
}
export function parseISUPPORT(tokens, params) {
var changed = [];
tokens.forEach((tok) => {
if (tok.startsWith("-")) {
var k = tok.slice(1);
@ -333,6 +334,168 @@ export function parseISUPPORT(tokens, params) {
k = tok.slice(0, i);
v = tok.slice(i + 1);
}
params.set(k.toUpperCase(), v);
k = k.toUpperCase();
params.set(k, v);
changed.push(k);
});
return changed;
}
export const CaseMapping = {
ASCII(str) {
var out = "";
for (var i = 0; i < str.length; i++) {
var ch = str[i];
if ("A" <= ch && ch <= "Z") {
ch = ch.toLowerCase();
}
out += ch;
}
return out;
},
RFC1459(str) {
var out = "";
for (var i = 0; i < str.length; i++) {
var ch = str[i];
if ("A" <= ch && ch <= "Z") {
ch = ch.toLowerCase();
} else if (ch == "{") {
ch = "[";
} else if (ch == "}") {
ch = "]";
} else if (ch == "\\") {
ch = "|";
} else if (ch == "~") {
ch = "^";
}
out += ch;
}
return out;
},
RFC1459Strict(str) {
var out = "";
for (var i = 0; i < str.length; i++) {
var ch = str[i];
if ("A" <= ch && ch <= "Z") {
ch = ch.toLowerCase();
} else if (ch == "{") {
ch = "[";
} else if (ch == "}") {
ch = "]";
} else if (ch == "\\") {
ch = "|";
}
out += ch;
}
return out;
},
byName(name) {
switch (name) {
case "ascii":
return CaseMapping.ASCII;
case "rfc1459":
return CaseMapping.RFC1459;
case "rfc1459-strict":
return CaseMapping.RFC1459Strict;
}
return null;
},
};
function createIterator(next) {
var it = { next };
// Not defining this can lead to surprises when feeding the iterator
// to e.g. Array.from
it[Symbol.iterator] = () => it;
return it;
}
function mapIterator(it, f) {
return createIterator(() => {
var { value, done } = it.next();
if (done) {
return { done: true };
}
return { value: f(value), done: false };
});
}
export class CaseMapMap {
caseMap = null;
map = null;
constructor(iterable, cm) {
if ((iterable instanceof CaseMapMap) && (iterable.caseMap === cm || !cm)) {
// Fast-path if we're just cloning another CaseMapMap
this.caseMap = iterable.caseMap;
this.map = new Map(iterable.map);
} else {
if (!cm) {
throw new Error("Missing case-mapping when creating CaseMapMap");
}
this.caseMap = cm;
this.map = new Map();
if (iterable) {
for (var [key, value] of iterable) {
this.set(key, value);
}
}
}
}
get size() {
return this.map.size;
}
has(key) {
return this.map.has(this.caseMap(key));
}
get(key) {
var kv = this.map.get(this.caseMap(key));
if (kv) {
return kv.value;
}
return undefined;
}
set(key, value) {
this.map.set(this.caseMap(key), { key, value });
}
delete(key) {
this.map.delete(this.caseMap(key));
}
entries() {
var it = this.map.values();
return mapIterator(it, (kv) => {
return [kv.key, kv.value];
});
}
keys() {
var it = this.map.values();
return mapIterator(it, (kv) => {
return kv.key;
});
}
values() {
var it = this.map.values();
return mapIterator(it, (kv) => {
return kv.value;
});
}
[Symbol.iterator]() {
return this.entries();
}
}