[Init] Initial commit - NetMesh terminal manager
Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled
Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled
This commit is contained in:
166
electron/plugins/contextKeys.cjs
Normal file
166
electron/plugins/contextKeys.cjs
Normal file
@@ -0,0 +1,166 @@
|
||||
"use strict";
|
||||
|
||||
const MAX_EXPRESSION_LENGTH = 2_048;
|
||||
const MAX_TOKENS = 256;
|
||||
const IDENTIFIER = /^[A-Za-z0-9_][A-Za-z0-9_.:-]{0,255}/u;
|
||||
const FULL_IDENTIFIER = /^[A-Za-z0-9_][A-Za-z0-9_.:-]{0,255}$/u;
|
||||
const PLUGIN_ID = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]*[a-z0-9])?)+$/u;
|
||||
const PLUGIN_CONTEXT_KEY_SUFFIX = /^[A-Za-z0-9_][A-Za-z0-9_:-]{0,255}$/u;
|
||||
|
||||
class ContextKeySyntaxError extends Error {
|
||||
constructor(message) {
|
||||
super(message);
|
||||
this.name = "ContextKeySyntaxError";
|
||||
}
|
||||
}
|
||||
|
||||
function tokenize(expression) {
|
||||
if (typeof expression !== "string" || expression.length < 1 || expression.length > MAX_EXPRESSION_LENGTH) {
|
||||
throw new ContextKeySyntaxError("Context Key expression length is invalid");
|
||||
}
|
||||
const tokens = [];
|
||||
let offset = 0;
|
||||
const push = (type, value = type) => {
|
||||
if (tokens.length >= MAX_TOKENS) throw new ContextKeySyntaxError("Context Key expression is too complex");
|
||||
tokens.push({ type, value });
|
||||
};
|
||||
while (offset < expression.length) {
|
||||
const rest = expression.slice(offset);
|
||||
const whitespace = /^\s+/u.exec(rest);
|
||||
if (whitespace) { offset += whitespace[0].length; continue; }
|
||||
const operator = /^(===|!==|&&|\|\||==|!=|>=|<=|>|<|!|\(|\))/u.exec(rest);
|
||||
if (operator) { push(operator[0]); offset += operator[0].length; continue; }
|
||||
const quoted = /^("(?:[^"\\]|\\["\\bfnrt]|\\u[0-9a-fA-F]{4})*"|'(?:[^'\\]|\\['\\bfnrt]|\\u[0-9a-fA-F]{4})*')/u.exec(rest);
|
||||
if (quoted) {
|
||||
const source = quoted[0];
|
||||
let value;
|
||||
try {
|
||||
value = source[0] === "\"" ? JSON.parse(source) : JSON.parse(`"${source.slice(1, -1).replace(/\\'/gu, "'").replace(/"/gu, '\\"')}"`);
|
||||
} catch { throw new ContextKeySyntaxError("Context Key string is invalid"); }
|
||||
push("literal", value);
|
||||
offset += source.length;
|
||||
continue;
|
||||
}
|
||||
const identifier = IDENTIFIER.exec(rest);
|
||||
const number = /^-?(?:0|[1-9]\d*)(?:\.\d+)?/u.exec(rest);
|
||||
if (number && (!identifier || number[0].length >= identifier[0].length)) {
|
||||
push("literal", Number(number[0]));
|
||||
offset += number[0].length;
|
||||
continue;
|
||||
}
|
||||
if (!identifier) throw new ContextKeySyntaxError(`Unexpected Context Key token at ${offset}`);
|
||||
offset += identifier[0].length;
|
||||
if (identifier[0] === "true") push("literal", true);
|
||||
else if (identifier[0] === "false") push("literal", false);
|
||||
else if (identifier[0] === "null") push("literal", null);
|
||||
else if (identifier[0] === "in") push("in");
|
||||
else if (identifier[0] === "not") push("not");
|
||||
else push("identifier", identifier[0]);
|
||||
}
|
||||
push("eof");
|
||||
return tokens;
|
||||
}
|
||||
|
||||
function parseContextKeyExpression(expression) {
|
||||
const tokens = tokenize(expression);
|
||||
let index = 0;
|
||||
const current = () => tokens[index];
|
||||
const consume = (type) => {
|
||||
if (current().type !== type) throw new ContextKeySyntaxError(`Expected ${type}`);
|
||||
return tokens[index++];
|
||||
};
|
||||
const primary = () => {
|
||||
if (current().type === "literal") return { type: "literal", value: consume("literal").value };
|
||||
if (current().type === "identifier") return { type: "key", name: consume("identifier").value };
|
||||
if (current().type === "(") {
|
||||
consume("(");
|
||||
const value = or();
|
||||
consume(")");
|
||||
return value;
|
||||
}
|
||||
throw new ContextKeySyntaxError("Expected Context Key value");
|
||||
};
|
||||
const unary = () => current().type === "!"
|
||||
? (consume("!"), { type: "not", value: unary() })
|
||||
: primary();
|
||||
const comparison = () => {
|
||||
const left = unary();
|
||||
let operator = current().type;
|
||||
if (operator === "not" && tokens[index + 1]?.type === "in") {
|
||||
consume("not"); consume("in"); operator = "not in";
|
||||
} else if (["==", "===", "!=", "!==", ">", ">=", "<", "<=", "in"].includes(operator)) {
|
||||
consume(operator);
|
||||
} else return left;
|
||||
return { type: "compare", operator, left, right: unary() };
|
||||
};
|
||||
const and = () => {
|
||||
let value = comparison();
|
||||
while (current().type === "&&") { consume("&&"); value = { type: "and", left: value, right: comparison() }; }
|
||||
return value;
|
||||
};
|
||||
const or = () => {
|
||||
let value = and();
|
||||
while (current().type === "||") { consume("||"); value = { type: "or", left: value, right: and() }; }
|
||||
return value;
|
||||
};
|
||||
const syntax = or();
|
||||
consume("eof");
|
||||
return Object.freeze(syntax);
|
||||
}
|
||||
|
||||
function contextValue(context, key) {
|
||||
if (context instanceof Map) return context.get(key);
|
||||
if (!context || typeof context !== "object") return undefined;
|
||||
return Object.hasOwn(context, key) ? context[key] : undefined;
|
||||
}
|
||||
|
||||
function evaluateSyntax(node, context) {
|
||||
if (node.type === "literal") return node.value;
|
||||
if (node.type === "key") return contextValue(context, node.name);
|
||||
if (node.type === "not") return !Boolean(evaluateSyntax(node.value, context));
|
||||
if (node.type === "and") return Boolean(evaluateSyntax(node.left, context)) && Boolean(evaluateSyntax(node.right, context));
|
||||
if (node.type === "or") return Boolean(evaluateSyntax(node.left, context)) || Boolean(evaluateSyntax(node.right, context));
|
||||
const left = evaluateSyntax(node.left, context);
|
||||
const right = evaluateSyntax(node.right, context);
|
||||
switch (node.operator) {
|
||||
case "==": case "===": return left === right;
|
||||
case "!=": case "!==": return left !== right;
|
||||
case ">": return typeof left === typeof right && left > right;
|
||||
case ">=": return typeof left === typeof right && left >= right;
|
||||
case "<": return typeof left === typeof right && left < right;
|
||||
case "<=": return typeof left === typeof right && left <= right;
|
||||
case "in": return Array.isArray(right) ? right.includes(left) : Boolean(right && typeof right === "object" && Object.hasOwn(right, String(left)));
|
||||
case "not in": return !(Array.isArray(right) ? right.includes(left) : Boolean(right && typeof right === "object" && Object.hasOwn(right, String(left))));
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
function evaluateContextKeyExpression(expression, context) {
|
||||
if (expression == null || expression === "") return true;
|
||||
try { return Boolean(evaluateSyntax(parseContextKeyExpression(expression), context)); }
|
||||
catch { return false; }
|
||||
}
|
||||
|
||||
function assertPluginContextKey(pluginId, key) {
|
||||
if (typeof pluginId !== "string" || pluginId.length > 128 || !PLUGIN_ID.test(pluginId)) {
|
||||
throw new TypeError("Plugin ID is invalid");
|
||||
}
|
||||
const prefix = `${pluginId}.`;
|
||||
const suffix = typeof key === "string" && key.startsWith(prefix)
|
||||
? key.slice(prefix.length)
|
||||
: "";
|
||||
if (typeof key !== "string" || !FULL_IDENTIFIER.test(key) || !PLUGIN_CONTEXT_KEY_SUFFIX.test(suffix)) {
|
||||
throw new TypeError(`Plugin Context Key must be namespaced to ${pluginId}`);
|
||||
}
|
||||
return key;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
ContextKeySyntaxError,
|
||||
MAX_EXPRESSION_LENGTH,
|
||||
MAX_TOKENS,
|
||||
assertPluginContextKey,
|
||||
evaluateContextKeyExpression,
|
||||
parseContextKeyExpression,
|
||||
tokenize,
|
||||
};
|
||||
Reference in New Issue
Block a user