dirty more detailed cmd defs

This commit is contained in:
LenAnderson
2024-04-23 09:03:28 -04:00
parent db5d2f13f9
commit d4b8094038
7 changed files with 952 additions and 108 deletions

View File

@ -1,10 +1,17 @@
import { SlashCommandArgument, SlashCommandNamedArgument } from './SlashCommandArgument.js';
export class SlashCommand {
/**@type {String}*/ name;
/**@type {string}*/ name;
/**@type {Function}*/ callback;
/**@type {String}*/ helpString;
/**@type {Boolean}*/ interruptsGeneration;
/**@type {Boolean}*/ purgeFromMessage;
/**@type {String[]}*/ aliases;
/**@type {string}*/ helpString;
/**@type {boolean}*/ interruptsGeneration = true;
/**@type {boolean}*/ purgeFromMessage = true;
/**@type {string[]}*/ aliases = [];
/**@type {string}*/ returns;
/**@type {SlashCommandNamedArgument[]}*/ namedArgumentList = [];
/**@type {SlashCommandArgument[]}*/ unnamedArgumentList = [];
get helpStringFormatted() {
let aliases = '';

View File

@ -0,0 +1,62 @@
import { SlashCommandClosure } from './SlashCommandClosure.js';
/**@readonly*/
/**@enum {string}*/
export const ARGUMENT_TYPE = {
'STRING': 'string',
'NUMBER': 'number',
'BOOLEAN': 'bool',
'VARIABLE_NAME': 'varname',
'CLOSURE': 'closure',
'SUBCOMMAND': 'subcommand',
'LIST': 'list',
'DICTIONARY': 'dictionary',
};
export class SlashCommandArgument {
/**@type {string}*/ description;
/**@type {ARGUMENT_TYPE[]}*/ typeList = [];
/**@type {boolean}*/ isRequired = false;
/**@type {boolean}*/ acceptsMultiple = false;
/**@type {string|SlashCommandClosure}*/ defaultValue;
/**@type {string[]}*/ enumList = [];
/**
* @param {string} description
* @param {ARGUMENT_TYPE|ARGUMENT_TYPE[]} types
* @param {string|SlashCommandClosure} defaultValue
* @param {string|string[]} enums
*/
constructor(description, types, isRequired = false, acceptsMultiple = false, defaultValue = null, enums = []) {
this.description = description;
this.typeList = types ? Array.isArray(types) ? types : [types] : [];
this.isRequired = isRequired ?? false;
this.acceptsMultiple = acceptsMultiple ?? false;
this.defaultValue = defaultValue;
this.enumList = enums ? Array.isArray(enums) ? enums : [enums] : [];
}
}
export class SlashCommandNamedArgument extends SlashCommandArgument {
/**@type {string}*/ name;
/**@type {string[]}*/ aliasList = [];
/**
* @param {string} name
* @param {string} description
* @param {ARGUMENT_TYPE|ARGUMENT_TYPE[]} types
* @param {string|SlashCommandClosure} defaultValue
* @param {string|string[]} enums
*/
constructor(name, description, types, isRequired = false, acceptsMultiple = false, defaultValue = null, enums = [], aliases = []) {
super(name, description, types, isRequired, acceptsMultiple, defaultValue, enums);
this.name = name;
this.aliasList = aliases ? Array.isArray(aliases) ? aliases : [aliases] : [];
}
}

View File

@ -45,4 +45,210 @@ export class SlashCommandAutoCompleteOption {
this.value = value;
this.name = name;
}
renderDetails() {
switch (this.type) {
case OPTION_TYPE.COMMAND: {
return this.renderCommandDetails();
}
case OPTION_TYPE.QUICK_REPLY: {
return this.renderQuickReplyDetails();
}
case OPTION_TYPE.VARIABLE_NAME: {
return this.renderVariableDetails();
}
default: {
return this.renderBlankDetails();
}
}
}
renderBlankDetails() {
return 'BLANK';
}
renderQuickReplyDetails() {
const frag = document.createDocumentFragment();
const specs = document.createElement('div'); {
specs.classList.add('specs');
const name = document.createElement('div'); {
name.classList.add('name');
name.classList.add('monospace');
name.textContent = this.value.toString();
specs.append(name);
}
frag.append(specs);
}
const help = document.createElement('span'); {
help.classList.add('help');
help.textContent = 'Quick Reply';
frag.append(help);
}
return frag;
}
renderVariableDetails() {
const frag = document.createDocumentFragment();
const specs = document.createElement('div'); {
specs.classList.add('specs');
const name = document.createElement('div'); {
name.classList.add('name');
name.classList.add('monospace');
name.textContent = this.value.toString();
specs.append(name);
}
frag.append(specs);
}
const help = document.createElement('span'); {
help.classList.add('help');
help.textContent = 'scoped variable';
frag.append(help);
}
return frag;
}
renderCommandDetails() {
const frag = document.createDocumentFragment();
const key = this.name;
/**@type {SlashCommand} */
// @ts-ignore
const cmd = this.value;
const namedArguments = cmd.namedArgumentList ?? [];
const unnamedArguments = cmd.unnamedArgumentList ?? [];
const returnType = cmd.returns ?? 'void';
const helpString = cmd.helpString ?? 'NO DETAILS';
const aliasList = cmd.aliases ?? [];
const specs = document.createElement('div'); {
specs.classList.add('specs');
const name = document.createElement('div'); {
name.classList.add('name');
name.classList.add('monospace');
name.textContent = `/${key}`;
specs.append(name);
}
const body = document.createElement('div'); {
body.classList.add('body');
const args = document.createElement('ul'); {
args.classList.add('arguments');
for (const arg of namedArguments) {
const listItem = document.createElement('li'); {
listItem.classList.add('argumentItem');
const argItem = document.createElement('div'); {
argItem.classList.add('argument');
argItem.classList.add('namedArgument');
if (!arg.isRequired || (arg.defaultValue ?? false)) argItem.classList.add('optional');
if (arg.acceptsMultiple) argItem.classList.add('multiple');
const name = document.createElement('span'); {
name.classList.add('argument-name');
name.textContent = arg.name;
argItem.append(name);
}
if (arg.enumList.length > 0) {
const enums = document.createElement('span'); {
enums.classList.add('argument-enums');
for (const e of arg.enumList) {
const enumItem = document.createElement('span'); {
enumItem.classList.add('argument-enum');
enumItem.textContent = e;
enums.append(enumItem);
}
}
argItem.append(enums);
}
} else {
const types = document.createElement('span'); {
types.classList.add('argument-types');
for (const t of arg.typeList) {
const type = document.createElement('span'); {
type.classList.add('argument-type');
type.textContent = t;
types.append(type);
}
}
argItem.append(types);
}
}
listItem.append(argItem);
}
const desc = document.createElement('div'); {
desc.classList.add('argument-description');
desc.innerHTML = arg.description;
listItem.append(desc);
}
args.append(listItem);
}
}
for (const arg of unnamedArguments) {
const listItem = document.createElement('li'); {
listItem.classList.add('argumentItem');
const argItem = document.createElement('div'); {
argItem.classList.add('argument');
argItem.classList.add('unnamedArgument');
if (!arg.isRequired || (arg.defaultValue ?? false)) argItem.classList.add('optional');
if (arg.acceptsMultiple) argItem.classList.add('multiple');
if (arg.enumList.length > 0) {
const enums = document.createElement('span'); {
enums.classList.add('argument-enums');
for (const e of arg.enumList) {
const enumItem = document.createElement('span'); {
enumItem.classList.add('argument-enum');
enumItem.textContent = e;
enums.append(enumItem);
}
}
argItem.append(enums);
}
} else {
const types = document.createElement('span'); {
types.classList.add('argument-types');
for (const t of arg.typeList) {
const type = document.createElement('span'); {
type.classList.add('argument-type');
type.textContent = t;
types.append(type);
}
}
argItem.append(types);
}
}
listItem.append(argItem);
}
const desc = document.createElement('div'); {
desc.classList.add('argument-description');
desc.innerHTML = arg.description;
listItem.append(desc);
}
args.append(listItem);
}
}
body.append(args);
}
const returns = document.createElement('span'); {
returns.classList.add('returns');
returns.textContent = returnType ?? 'void';
body.append(returns);
}
specs.append(body);
}
frag.append(specs);
}
const help = document.createElement('span'); {
help.classList.add('help');
help.innerHTML = helpString;
frag.append(help);
}
if (aliasList.length > 0) {
const aliases = document.createElement('span'); {
aliases.classList.add('aliases');
aliases.append(' (alias: ');
for (const aliasName of aliasList) {
const alias = document.createElement('span'); {
alias.classList.add('monospace');
alias.textContent = `/${aliasName}`;
aliases.append(alias);
}
}
aliases.append(')');
frag.append(aliases);
}
}
return frag;
}
}

View File

@ -1,6 +1,7 @@
import { power_user } from '../power-user.js';
import { isTrueBoolean, uuidv4 } from '../utils.js';
import { SlashCommand } from './SlashCommand.js';
import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from './SlashCommandArgument.js';
import { OPTION_TYPE, SlashCommandAutoCompleteOption } from './SlashCommandAutoCompleteOption.js';
import { SlashCommandClosure } from './SlashCommandClosure.js';
import { SlashCommandExecutor } from './SlashCommandExecutor.js';
@ -18,7 +19,66 @@ export const PARSER_FLAG = {
export class SlashCommandParser {
// @ts-ignore
/**@type {Object.<string, SlashCommand>}*/ commands = {};
/**@type {Object.<string, SlashCommand>}*/ static commands = {};
static addCommand(command, callback, aliases, helpString = '', interruptsGeneration = false, purgeFromMessage = true) {
const reserved = ['/', '#', ':', 'parser-flag'];
for (const start of reserved) {
if (command.toLowerCase().startsWith(start) || (aliases ?? []).find(a=>a.toLowerCase().startsWith(start))) {
throw new Error(`Illegal Name. Slash command name cannot begin with "${start}".`);
}
}
this.addCommandUnsafe(command, callback, aliases, helpString, interruptsGeneration, purgeFromMessage);
}
static addCommandUnsafe(command, callback, aliases, helpString = '', interruptsGeneration = false, purgeFromMessage = true) {
const fnObj = Object.assign(new SlashCommand(), { name:command, callback, helpString, interruptsGeneration, purgeFromMessage, aliases });
if ([command, ...aliases].some(x => Object.hasOwn(this.commands, x))) {
console.trace('WARN: Duplicate slash command registered!', [command, ...aliases]);
}
this.commands[command] = fnObj;
if (Array.isArray(aliases)) {
aliases.forEach((alias) => {
this.commands[alias] = fnObj;
});
}
}
/**
*
* @param {SlashCommand} command
*/
static addCommandObject(command) {
const reserved = ['/', '#', ':', 'parser-flag'];
for (const start of reserved) {
if (command.name.toLowerCase().startsWith(start) || (command.aliases ?? []).find(a=>a.toLowerCase().startsWith(start))) {
throw new Error(`Illegal Name. Slash command name cannot begin with "${start}".`);
}
}
this.addCommandObjectUnsafe(command);
}
/**
*
* @param {SlashCommand} command
*/
static addCommandObjectUnsafe(command) {
if ([command.name, ...command.aliases].some(x => Object.hasOwn(this.commands, x))) {
console.trace('WARN: Duplicate slash command registered!', [command.name, ...command.aliases]);
}
this.commands[command.name] = command;
if (Array.isArray(command.aliases)) {
command.aliases.forEach((alias) => {
this.commands[alias] = command;
});
}
}
get commands() {
return SlashCommandParser.commands;
}
// @ts-ignore
/**@type {Object.<string, string>}*/ helpStrings = {};
/**@type {boolean}*/ verifyCommandNames = true;
@ -54,14 +114,68 @@ export class SlashCommandParser {
constructor() {
// add dummy commands for help strings / autocomplete
this.addDummyCommand('parser-flag',
[],
`<span class="monospace">(${Object.keys(PARSER_FLAG).join('|')}) (on|off)</span> Set a parser flag.`,
);
this.addDummyCommand('/',
['#'],
'<span class="monospace">(comment)</span> Write a comment.',
);
const parserFlagCmd = new SlashCommand();
parserFlagCmd.name = 'parser-flag';
parserFlagCmd.unnamedArgumentList.push(new SlashCommandArgument(
'flag',
'The parser flag to modify.',
ARGUMENT_TYPE.STRING,
true,
false,
null,
Object.keys(PARSER_FLAG),
));
parserFlagCmd.unnamedArgumentList.push(new SlashCommandArgument(
'state',
'The state of the parser flag to set.',
ARGUMENT_TYPE.BOOLEAN,
false,
false,
'on',
// ['on', 'off'],
));
parserFlagCmd.helpString = 'Set a parser flag.';
SlashCommandParser.addCommandObjectUnsafe(parserFlagCmd);
const commentCmd = new SlashCommand();
commentCmd.name = '/';
commentCmd.aliases.push('#');
commentCmd.unnamedArgumentList.push(new SlashCommandArgument(
'comment',
'Commentary',
ARGUMENT_TYPE.STRING,
));
commentCmd.helpString = 'Write a comment.';
SlashCommandParser.addCommandObjectUnsafe(commentCmd);
const dummyCmd = new SlashCommand();
dummyCmd.name = 'foo';
dummyCmd.namedArgumentList.push(new SlashCommandNamedArgument(
'arg1', 'first argument', ARGUMENT_TYPE.STRING, true,
));
dummyCmd.namedArgumentList.push(new SlashCommandNamedArgument(
'arg2', 'second argument', ARGUMENT_TYPE.STRING, true,
));
dummyCmd.namedArgumentList.push(new SlashCommandNamedArgument(
'arg3', 'third argument', ARGUMENT_TYPE.STRING, false, false,
));
dummyCmd.namedArgumentList.push(new SlashCommandNamedArgument(
'arg3', 'third argument', ARGUMENT_TYPE.STRING, false, false,
));
dummyCmd.namedArgumentList.push(new SlashCommandNamedArgument(
'arg3', 'third argument', ARGUMENT_TYPE.STRING, false, false,
));
dummyCmd.namedArgumentList.push(new SlashCommandNamedArgument(
'arg3', 'third argument', ARGUMENT_TYPE.STRING, false, false,
));
dummyCmd.unnamedArgumentList.push(new SlashCommandArgument(
'foo', 'foo argument', [ARGUMENT_TYPE.SUBCOMMAND, ARGUMENT_TYPE.CLOSURE], true, true,
));
dummyCmd.callback = ()=>'foo';
dummyCmd.returns = 'string';
dummyCmd.helpString = 'Just a dummy command that does not do anything but return "foo".';
SlashCommandParser.addCommandObjectUnsafe(dummyCmd);
this.registerLanguage();
}
registerLanguage() {
@ -165,31 +279,10 @@ export class SlashCommandParser {
}
addCommand(command, callback, aliases, helpString = '', interruptsGeneration = false, purgeFromMessage = true) {
const reserved = ['/', '#', ':', 'parser-flag'];
for (const start of reserved) {
if (command.toLowerCase().startsWith(start) || (aliases ?? []).find(a=>a.toLowerCase().startsWith(reserved))) {
throw new Error(`Illegal Name. Slash command name cannot begin with "${start}".`);
}
}
this.addCommandUnsafe(command, callback, aliases, helpString, interruptsGeneration, purgeFromMessage);
}
addCommandUnsafe(command, callback, aliases, helpString = '', interruptsGeneration = false, purgeFromMessage = true) {
const fnObj = Object.assign(new SlashCommand(), { name:command, callback, helpString, interruptsGeneration, purgeFromMessage, aliases });
if ([command, ...aliases].some(x => Object.hasOwn(this.commands, x))) {
console.trace('WARN: Duplicate slash command registered!', [command, ...aliases]);
}
this.commands[command] = fnObj;
if (Array.isArray(aliases)) {
aliases.forEach((alias) => {
this.commands[alias] = fnObj;
});
}
SlashCommandParser.addCommand(command, callback, aliases, helpString, interruptsGeneration, purgeFromMessage);
}
addDummyCommand(command, aliases, helpString) {
this.addCommandUnsafe(command, null, aliases, helpString, true, true);
SlashCommandParser.addCommandUnsafe(command, null, aliases, helpString, true, true);
}
getHelpString() {
@ -469,7 +562,7 @@ export class SlashCommandParser {
return this.testCommandEnd();
}
parseComment() {
const start = this.index + 2;
const start = this.index + 1;
const cmd = new SlashCommandExecutor(start);
this.commandIndex.push(cmd);
this.scopeIndex.push(this.scope.getCopy());