Skip to content

Commit

Permalink
Update SuperCoolStream for eslint and ts5.
Browse files Browse the repository at this point in the history
  • Loading branch information
Gnuxie committed Jul 17, 2024
1 parent 18bcb01 commit cff1908
Show file tree
Hide file tree
Showing 9 changed files with 114 additions and 66 deletions.
4 changes: 2 additions & 2 deletions src/Draupnir.ts
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,7 @@ export class Draupnir implements Client {
this.protectedRoomsSet.handleExternalInvite(roomID, event);
}
this.managementRoomMessageListener(roomID, event);
this.reactionHandler.handleEvent(roomID, event);
void Task((async () => { await this.reactionHandler.handleEvent(roomID, event); })());
if (this.protectedRoomsSet.isProtectedRoom(roomID)) {
this.protectedRoomsSet.handleTimelineEvent(roomID, event);
}
Expand All @@ -248,7 +248,7 @@ export class Draupnir implements Client {
}
if (Value.Check(RoomMessage, event) && Value.Check(TextMessageContent, event.content)) {
if (event.content.body === "** Unable to decrypt: The sender's device has not sent us the keys for this message. **") {
log.info(`Unable to decrypt an event ${event.event_id} from ${event.sender} in the management room ${this.managementRoom}.`);
log.info(`Unable to decrypt an event ${event.event_id} from ${event.sender} in the management room ${this.managementRoom.toPermalink()}.`);
void Task(this.client.unstableApis.addReactionToEvent(roomID, event.event_id, '⚠').then(_ => Ok(undefined)));
void Task(this.client.unstableApis.addReactionToEvent(roomID, event.event_id, 'UISI').then(_ => Ok(undefined)));
void Task(this.client.unstableApis.addReactionToEvent(roomID, event.event_id, '🚨').then(_ => Ok(undefined)));
Expand Down
4 changes: 2 additions & 2 deletions src/ManagementRoomOutput.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ export default class ManagementRoomOutput {

// Though spec doesn't say so, room ids that have slashes in them are accepted by Synapse and Dendrite unfortunately for us.
const escapeRegex = (v: string): string => {
return v.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
return v.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&');
};

const viaServers = [serverName(this.clientUserID)];
Expand Down Expand Up @@ -108,7 +108,7 @@ export default class ManagementRoomOutput {
* @param additionalRoomIds The roomIds in the message that we want to be replaced by room pills.
* @param isRecursive Whether logMessage is being called from logMessage.
*/
public async logMessage(level: LogLevel, module: string, message: string | any, additionalRoomIds: string[] | string | null = null, isRecursive = false): Promise<any> {
public async logMessage(level: LogLevel, module: string, message: string, additionalRoomIds: string[] | string | null = null, isRecursive = false): Promise<void> {
if (level === LogLevel.ERROR) {
Sentry.captureMessage(`${module}: ${message}`, 'error');
}
Expand Down
4 changes: 2 additions & 2 deletions src/appservice/bot/AppserviceCommandHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { defineMatrixInterfaceAdaptor, findMatrixInterfaceAdaptor, MatrixContext
import { ArgumentStream, RestDescription, findPresentationType, parameters } from '../../commands/interface-manager/ParameterParsing';
import { MjolnirAppService } from '../AppService';
import { renderHelp } from '../../commands/interface-manager/MatrixHelpRenderer';
import { ActionResult, ClientPlatform, Ok, RoomMessage, StringRoomID, StringUserID, Value, isError } from 'matrix-protection-suite';
import { ActionResult, ClientPlatform, Ok, RoomMessage, StringRoomID, StringUserID, Task, Value, isError } from 'matrix-protection-suite';
import { MatrixSendClient } from 'matrix-protection-suite-for-matrix-bot-sdk';
import { MatrixReactionHandler } from '../../commands/interface-manager/MatrixReactionHandler';
import { ARGUMENT_PROMPT_LISTENER, DEFAUILT_ARGUMENT_PROMPT_LISTENER, makeListenerForArgumentPrompt, makeListenerForPromptDefault } from '../../commands/interface-manager/MatrixPromptForAccept';
Expand Down Expand Up @@ -105,7 +105,7 @@ export class AppserviceCommandHandler {
...this.commandContext,
event: parsedEvent,
};
adaptor.invoke(context, context, ...argumentStream.rest());
void Task(adaptor.invoke(context, context, ...argumentStream.rest()));
return;
}
}
Expand Down
6 changes: 3 additions & 3 deletions src/appservice/bot/ListCommand.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { AppserviceBaseExecutor } from './AppserviceCommandHandler';
import { tickCrossRenderer } from '../../commands/interface-manager/MatrixHelpRenderer';
import { DeadDocumentJSX } from '../../commands/interface-manager/JSXFactory';
import { renderMatrixAndSend } from '../../commands/interface-manager/DeadDocumentMatrix';
import { ActionError, ActionResult, isError, Ok, UserID } from 'matrix-protection-suite';
import { ActionError, ActionResult, isError, Ok, RoomEvent, UserID } from 'matrix-protection-suite';
import { MatrixSendClient } from 'matrix-protection-suite-for-matrix-bot-sdk';
import { UnstartedDraupnir } from '../../draupnirfactory/StandardDraupnirManager';

Expand All @@ -37,7 +37,7 @@ const listUnstarted = defineInterfaceCommand<AppserviceBaseExecutor>({
// and be used similar to like #=1 and #1.
defineMatrixInterfaceAdaptor({
interfaceCommand: listUnstarted,
renderer: async function (this: MatrixInterfaceAdaptor<MatrixContext>, client: MatrixSendClient, commandRoomId: string, event: any, result: ActionResult<UnstartedDraupnir[]>) {
renderer: async function (this: MatrixInterfaceAdaptor<MatrixContext>, client: MatrixSendClient, commandRoomId: string, event: RoomEvent, result: ActionResult<UnstartedDraupnir[]>) {
tickCrossRenderer.call(this, client, commandRoomId, event, result); // don't await, it doesn't really matter.
if (isError(result)) {
return; // just let the default handler deal with it.
Expand Down Expand Up @@ -82,7 +82,7 @@ const restart = defineInterfaceCommand<AppserviceBaseExecutor>({
const draupnirManager = this.appservice.draupnirManager;
const draupnir = draupnirManager.findUnstartedDraupnir(draupnirUser.toString());
if (draupnir !== undefined) {
return ActionError.Result(`We can't find the unstarted draupnir ${draupnirUser}, is it already running?`);
return ActionError.Result(`We can't find the unstarted draupnir ${draupnirUser.toString()}, is it already running?`);
}
return await draupnirManager.startDraupnirFromMXID(draupnirUser.toString());
},
Expand Down
1 change: 1 addition & 0 deletions src/appservice/postgres/PgDataStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ function getSchema(): SchemaUpdateFunction[] {
const nSchema = 2;
const schema = [];
for (let schemaID = 1; schemaID < nSchema + 1; schemaID++) {
// eslint-disable-next-line @typescript-eslint/no-var-requires
schema.push(require(`./schema/v${schemaID}`).runSchema);
}
return schema;
Expand Down
128 changes: 84 additions & 44 deletions src/commands/interface-manager/CommandReader.ts
Original file line number Diff line number Diff line change
@@ -1,63 +1,98 @@
/**
* Copyright (C) 2022 Gnuxie <Gnuxie@protonmail.com>
* All rights reserved.
*/
// SPDX-FileCopyrightText: 2022 - 2024 Gnuxie <Gnuxie@protonmail.com>
//
// SPDX-License-Identifier: Apache-2.0
//
// SPDX-FileAttributionText: <text>
// This modified file incorporates work from super-cool-stream
// https://github.com/Gnuxie/super-cool-stream
// </text>

import { MatrixEventReference, MatrixRoomReference, Permalinks, UserID, isError, isStringRoomAlias, isStringRoomID } from "matrix-protection-suite";
export interface SuperCoolStream<Item, Sequence> {
readonly source: Sequence;
peekItem<EOF = undefined>(eof?: EOF): Item | EOF;
readItem<EOF = undefined>(eof?: EOF): Item | EOF;
getPosition(): number;
setPosition(n: number): void;
clone(): SuperCoolStream<Item, Sequence>;
savingPositionIf<Result>(description: {
predicate: (t: Result) => boolean;
body: (stream: SuperCoolStream<Item, Sequence>) => Result;
}): Result;
}

export interface ISuperCoolStream<T> {
readonly source: T
peekItem(eof?: any): T|any,
readItem(eof?: any): T|any,
getPosition(): number,
savingPositionIf<Result>(description: { predicate: (t: Result) => boolean, body: (stream: ISuperCoolStream<T>) => Result}): Result;
}
interface Indexable<Item> {
at(position: number): Item | undefined;
}

export class SuperCoolStream<T extends { at: (...args: any) => any|undefined}> implements ISuperCoolStream<T> {
protected position: number
export class StandardSuperCoolStream<Item, Sequence extends Indexable<Item>>
implements SuperCoolStream<Item, Sequence>
{
protected position: number;
/**
* Makes the super cool string stream.
* @param source A string to act as the source of the stream.
* @param start Where in the string we should start reading.
*/
constructor(public readonly source: T, start = 0) {
this.position = start;
constructor(
public readonly source: Sequence,
start = 0
) {
this.position = start;
}

public peekItem(eof = undefined) {
return this.source.at(this.position) ?? eof;
public peekItem<EOF = undefined>(eof?: EOF): Item | EOF {
return this.source.at(this.position) ?? (eof as EOF);
}

public readItem(eof = undefined) {
return this.source.at(this.position++) ?? eof;
public readItem<EOF = undefined>(eof?: EOF) {
return this.source.at(this.position++) ?? (eof as EOF);
}

public getPosition(): number {
return this.position;
return this.position;
}

savingPositionIf<Result>(description: { predicate: (t: Result) => boolean; body: (stream: SuperCoolStream<T>) => Result; }): Result {
const previousPosition = this.position;
const bodyResult = description.body(this);
if (description.predicate(bodyResult)) {
this.position = previousPosition;
}
return bodyResult;
public setPosition(n: number) {
this.position = n;
}
}

/**
* Helper for peeking and reading character by character.
*/
class StringStream extends SuperCoolStream<string> {
public peekChar(...args: any[]) {
return this.peekItem(...args);
public clone(): SuperCoolStream<Item, Sequence> {
return new StandardSuperCoolStream(this.source, this.position);
}

public readChar(...args: any[]) {
return this.readItem(...args);
savingPositionIf<Result>(description: {
predicate: (t: Result) => boolean;
body: (stream: SuperCoolStream<Item, Sequence>) => Result;
}): Result {
const previousPosition = this.position;
const bodyResult = description.body(this);
if (description.predicate(bodyResult)) {
this.position = previousPosition;
}
return bodyResult;
}
}
}

/**
* Helper for peeking and reading character by character.
*/
export class StringStream extends StandardSuperCoolStream<
string,
Indexable<string>
> {
public peekChar<EOF = undefined>(eof?: EOF) {
return this.peekItem(eof);
}

public readChar<EOF = undefined>(eof?: EOF) {
return this.readItem(eof);
}

public clone(): StringStream {
return new StringStream(this.source, this.position);
}
}

/** Whitespace we want to nom. */
const WHITESPACE = [' ', '\r', '\f', '\v', '\n', '\t'];
Expand Down Expand Up @@ -95,8 +130,10 @@ export function readCommand(string: string): ReadItem[] {

function readCommandFromStream(stream: StringStream): ReadItem[] {
const words: ReadItem[] = [];
while (stream.peekChar() !== undefined && (eatWhitespace(stream), true) && stream.peekChar() !== undefined) {
eatWhitespace(stream);
while (stream.peekChar() !== undefined) {
words.push(readItem(stream));
eatWhitespace(stream);
}
return words.map(applyPostReadTransformersToReadItem);
}
Expand All @@ -117,13 +154,16 @@ function readItem(stream: StringStream): ReadItem {
if (WHITESPACE.includes(stream.peekChar())) {
throw new TypeError('whitespace should have been stripped');
}
const dispatchCharacter = stream.peekChar()!;
const dispatchCharacter = stream.peekChar();
if (dispatchCharacter === undefined) {
throw new TypeError(`There should be a dispatch character and if there isn't then the code is wrong`);
}
const macro = WORD_DISPATCH_CHARACTERS.get(dispatchCharacter);
if (macro) {
return macro(stream);
} else {
// Then read a normal word.
const word: string[] = [stream.readChar()!];
const word: string[] = [stream.readChar()];
readUntil(/\s/, stream, word);
return word.join('');
}
Expand Down Expand Up @@ -173,7 +213,7 @@ function definePostReadReplace(regex: RegExp, transformer: PostReadStringReplace

function applyPostReadTransformersToReadItem(item: ReadItem): ReadItem {
if (typeof item === 'string') {
for (const [_key, { regex, transformer }] of POST_READ_TRANSFORMERS) {
for (const [, { regex, transformer }] of POST_READ_TRANSFORMERS) {
if (regex.test(item)) {
return transformer(item);
}
Expand Down Expand Up @@ -203,7 +243,7 @@ function readUntil(regex: RegExp, stream: StringStream, output: string[]) {
* @returns A MatrixRoomReference or string if what has been read does not represent a room.
*/
function readRoomIDOrAlias(stream: StringStream): MatrixRoomReference|string {
const word: string[] = [stream.readChar()!];
const word: string[] = [stream.readChar()];
readUntil(/[:\s]/, stream, word);
if (stream.peekChar() === undefined || WHITESPACE.includes(stream.peekChar())) {
return word.join('');
Expand All @@ -226,7 +266,7 @@ defineReadItem('!', readRoomIDOrAlias);
* Read the word as a UserID, otherwise return a string if what has been read doesn not represent a user.
*/
defineReadItem('@', (stream: StringStream): UserID|string => {
const word: string[] = [stream.readChar()!];
const word: string[] = [stream.readChar()];
readUntil(/[:\s]/, stream, word);
if (stream.peekChar() === undefined || WHITESPACE.includes(stream.peekChar())) {
return word.join('');
Expand Down Expand Up @@ -260,7 +300,7 @@ function readKeyword(stream: StringStream): Keyword {
if (stream.peekChar() === undefined) {
return new Keyword('');
}
const word: string[] = [stream.readChar()!]
const word: string[] = [stream.readChar()]
readUntil(/[\s]/, stream, word)
return new Keyword(word.join(''));
}
Expand Down
7 changes: 5 additions & 2 deletions src/commands/interface-manager/DeadDocument.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* All rights reserved.
*/

import { SuperCoolStream } from "./CommandReader";
import { StandardSuperCoolStream } from "./CommandReader";

/**
* The DeadDocument started as a universal document object model like Pandoc is.
Expand Down Expand Up @@ -265,7 +265,7 @@ const COMMITTABLE_NODES = new Set([
NodeTag.Root,
]);

class FringeStream extends SuperCoolStream<Flat> {
class FringeStream extends StandardSuperCoolStream<AnnotatedFringeNode, Flat> {

}

Expand Down Expand Up @@ -315,6 +315,9 @@ export class FringeWalker<Context> {
}
while (this.stream.peekItem() && !isAnnotatedNodeCommittable(this.stream.peekItem())) {
const annotatedNode = this.stream.readItem();
if (annotatedNode === undefined) {
throw new TypeError(`Stream code is wrong`);
}
switch (annotatedNode.type) {
case FringeType.Pre:
renderInnerNode(annotatedNode);
Expand Down
22 changes: 13 additions & 9 deletions src/commands/interface-manager/ParameterParsing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,12 @@ limitations under the License.
*/

import { ActionError, ActionResult, Ok, ResultError, isError } from "matrix-protection-suite";
import { ISuperCoolStream, Keyword, ReadItem, SuperCoolStream } from "./CommandReader";
import { Keyword, ReadItem, StandardSuperCoolStream, SuperCoolStream } from "./CommandReader";
import { PromptOptions } from "./PromptForAccept";
import { PromptRequiredError } from "./PromptRequiredError";
import { printReadably } from "./PrintReadably";

export interface IArgumentStream extends ISuperCoolStream<ReadItem[]> {
export interface IArgumentStream extends SuperCoolStream<ReadItem, ReadItem[]> {
rest(): ReadItem[],
// All of the read items before the current position.
priorItems(): ReadItem[],
Expand All @@ -38,7 +39,7 @@ export interface IArgumentStream extends ISuperCoolStream<ReadItem[]> {
prompt(parameterDescription: ParameterDescription): Promise<ActionResult<ReadItem>>,
}

export class ArgumentStream extends SuperCoolStream<ReadItem[]> implements IArgumentStream {
export class ArgumentStream extends StandardSuperCoolStream<ReadItem, ReadItem[]> implements IArgumentStream {
public rest() {
return this.source.slice(this.position);
}
Expand Down Expand Up @@ -96,7 +97,7 @@ export function simpleTypeValidator(name: string, predicate: (readItem: ReadItem
return Ok(result);
} else {
// How do we accurately denote the type when it includes spaces in its name, same for the read item?
return ActionError.Result(`Was expecting a match for the presentation type: ${name} but got ${readItem}.`);
return ActionError.Result(`Was expecting a match for the presentation type: ${name} but got ${printReadably(readItem)}.`);
}
}
}
Expand All @@ -115,6 +116,7 @@ export function presentationTypeOf(presentation: unknown): PresentationType|unde
} else {
// until there are subtype semantics we have to fail early so that we have a chance of knowing
// that we have a conflicting type.
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
throw new TypeError(`presentationTypeof: There are multiple candidates for the presentation ${presentation}: ${JSON.stringify(candidates.map(c => c.name))}`)
}
}
Expand Down Expand Up @@ -166,7 +168,7 @@ export class RestDescription<ExecutorContext = unknown> implements ParameterDesc
*/
public async parseRest(stream: IArgumentStream, promptForRest: boolean, keywordParser: KeywordParser): Promise<ActionResult<ReadItem[]>> {
const items: ReadItem[] = [];
if (this.prompt && promptForRest && stream.isPromptable() && stream.peekItem() === undefined) {
if (this.prompt && promptForRest && stream.isPromptable() && stream.peekItem(undefined) === undefined) {
return PromptRequiredError.Result(
`A prompt is required for the missing argument for the ${this.name} parameter`,
{
Expand All @@ -175,20 +177,22 @@ export class RestDescription<ExecutorContext = unknown> implements ParameterDesc
}
);
}
while (stream.peekItem() !== undefined) {
while (stream.peekItem(undefined) !== undefined) {
const keywordResult = keywordParser.parseKeywords(stream);
if (isError(keywordResult)) {
return keywordResult;
}
if (stream.peekItem() !== undefined) {
const validationResult = this.acceptor.validator(stream.peekItem());
const keywordValue = stream.peekItem(undefined);
if (keywordValue !== undefined) {
const validationResult = this.acceptor.validator(keywordValue);
if (isError(validationResult)) {
return ArgumentParseError.Result(
validationResult.error.message,
{ parameter: this, stream }
);
}
items.push(stream.readItem());
items.push(keywordValue);
stream.readItem(); // dispose of keyword's associated value from the stream.
}
}
return Ok(items);
Expand Down
Loading

0 comments on commit cff1908

Please sign in to comment.