Add type conversion for /setvar commands with index

This commit is contained in:
Cohee 2024-08-29 12:55:54 +00:00
parent 7aaabbd2bb
commit 3746f08590
4 changed files with 166 additions and 39 deletions

View File

@ -36,6 +36,8 @@ export const enumIcons = {
true: '✔️',
false: '❌',
null: '🚫',
undefined: '❓',
// Value types
boolean: '🔲',
@ -230,4 +232,19 @@ export const commonEnumProviders = {
enumTypes.enum, '💉');
});
},
/**
* Gets somewhat recognizable STscript types.
*
* @returns {SlashCommandEnumValue[]}
*/
types: () => [
new SlashCommandEnumValue('string', null, enumTypes.type, enumIcons.string),
new SlashCommandEnumValue('number', null, enumTypes.type, enumIcons.number),
new SlashCommandEnumValue('boolean', null, enumTypes.type, enumIcons.boolean),
new SlashCommandEnumValue('array', null, enumTypes.type, enumIcons.array),
new SlashCommandEnumValue('object', null, enumTypes.type, enumIcons.dictionary),
new SlashCommandEnumValue('null', null, enumTypes.type, enumIcons.null),
new SlashCommandEnumValue('undefined', null, enumTypes.type, enumIcons.undefined),
],
};

View File

@ -1,4 +1,5 @@
import { SlashCommandClosure } from './SlashCommandClosure.js';
import { convertValueType } from '../utils.js';
export class SlashCommandScope {
/**@type {string[]}*/ variableNames = [];
@ -55,7 +56,8 @@ export class SlashCommandScope {
if (this.existsVariableInScope(key)) throw new SlashCommandScopeVariableExistsError(`Variable named "${key}" already exists.`);
this.variables[key] = value;
}
setVariable(key, value, index = null) {
setVariable(key, value, index = null, type = null) {
value = convertValueType(value, type);
if (this.existsVariableInScope(key)) {
if (index !== null && index !== undefined) {
let v = this.variables[key];

View File

@ -4,6 +4,7 @@ import { isMobile } from './RossAscends-mods.js';
import { collapseNewlines } from './power-user.js';
import { debounce_timeout } from './constants.js';
import { Popup, POPUP_RESULT, POPUP_TYPE } from './popup.js';
import { SlashCommandClosure } from './slash-commands/SlashCommandClosure.js';
/**
* Pagination status string template.
@ -33,6 +34,74 @@ export function isValidUrl(value) {
}
}
/**
* Converts string to a value of a given type. Includes pythonista-friendly aliases.
* @param {string|SlashCommandClosure} value String value
* @param {string} type Type to convert to
* @returns {any} Converted value
*/
export function convertValueType(value, type) {
if (value instanceof SlashCommandClosure || typeof type !== 'string') {
return value;
}
type = type.trim().toLowerCase();
switch (type) {
case 'string':
case 'str':
return String(value);
case 'null':
return null;
case 'undefined':
case 'none':
return undefined;
case 'number':
return Number(value);
case 'int':
return parseInt(value, 10);
case 'float':
return parseFloat(value);
case 'boolean':
case 'bool':
return isTrueBoolean(value);
case 'list':
case 'array':
try {
const parsedArray = JSON.parse(value);
if (Array.isArray(parsedArray)) {
return parsedArray;
}
throw new Error('Value is not an array.');
} catch {
return [];
}
case 'object':
case 'dict':
case 'dictionary':
try {
const parsedObject = JSON.parse(value);
if (typeof parsedObject === 'object') {
return parsedObject;
}
throw new Error('Value is not an object.');
} catch {
return {};
}
default:
return value;
}
}
/**
* Parses ranges like 10-20 or 10.
* Range is inclusive. Start must be less than end.

View File

@ -11,7 +11,7 @@ import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCom
import { SlashCommandEnumValue, enumTypes } from './slash-commands/SlashCommandEnumValue.js';
import { PARSER_FLAG, SlashCommandParser } from './slash-commands/SlashCommandParser.js';
import { SlashCommandScope } from './slash-commands/SlashCommandScope.js';
import { isFalseBoolean } from './utils.js';
import { isFalseBoolean, convertValueType } from './utils.js';
/** @typedef {import('./slash-commands/SlashCommandParser.js').NamedArguments} NamedArguments */
/** @typedef {import('./slash-commands/SlashCommand.js').UnnamedArguments} UnnamedArguments */
@ -51,6 +51,7 @@ function setLocalVariable(name, value, args = {}) {
if (args.index !== undefined) {
try {
value = convertValueType(value, args.type);
let localVariable = JSON.parse(chat_metadata.variables[name] ?? 'null');
const numIndex = Number(args.index);
if (Number.isNaN(numIndex)) {
@ -100,6 +101,7 @@ function getGlobalVariable(name, args = {}) {
function setGlobalVariable(name, value, args = {}) {
if (args.index !== undefined) {
try {
value = convertValueType(value, args.type);
let globalVariable = JSON.parse(extension_settings.variables.global[name] ?? 'null');
const numIndex = Number(args.index);
if (Number.isNaN(numIndex)) {
@ -667,23 +669,28 @@ function parseNumericSeries(value, scope = null) {
}
function performOperation(value, operation, singleOperand = false, scope = null) {
if (!value) {
return 0;
function getResult() {
if (!value) {
return 0;
}
const array = parseNumericSeries(value, scope);
if (array.length === 0) {
return 0;
}
const result = singleOperand ? operation(array[0]) : operation(array);
if (isNaN(result) || !isFinite(result)) {
return 0;
}
return result;
}
const array = parseNumericSeries(value, scope);
if (array.length === 0) {
return 0;
}
const result = singleOperand ? operation(array[0]) : operation(array);
if (isNaN(result) || !isFinite(result)) {
return 0;
}
return result;
const result = getResult();
return String(result);
}
function addValuesCallback(args, value) {
@ -836,7 +843,7 @@ function varCallback(args, value) {
if (typeof key != 'string') throw new Error('Key must be a string');
if (args._hasUnnamedArgument) {
const val = typeof value[0] == 'string' ? value.join(' ') : value[0];
args._scope.setVariable(key, val, args.index);
args._scope.setVariable(key, val, args.index, args.type);
return val;
} else {
return args._scope.getVariable(key, args.index);
@ -846,7 +853,7 @@ function varCallback(args, value) {
if (typeof key != 'string') throw new Error('Key must be a string');
if (value.length > 0) {
const val = typeof value[0] == 'string' ? value.join(' ') : value[0];
args._scope.setVariable(key, val, args.index);
args._scope.setVariable(key, val, args.index, args.type);
return val;
} else {
return args._scope.getVariable(key, args.index);
@ -901,6 +908,14 @@ export function registerVariableCommands() {
new SlashCommandNamedArgument(
'index', 'list index', [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.STRING], false,
),
SlashCommandNamedArgument.fromProps({
name: 'type',
description: 'type of the value when used with index',
forceEnum: true,
enumProvider: commonEnumProviders.types,
isRequired: false,
defaultValue: 'string',
}),
],
unnamedArgumentList: [
new SlashCommandArgument(
@ -910,6 +925,7 @@ export function registerVariableCommands() {
helpString: `
<div>
Set a local variable value and pass it down the pipe. The <code>index</code> argument is optional.
To perform a type conversion when using <code>index</code>, use the <code>type</code> argument.
</div>
<div>
<strong>Example:</strong>
@ -917,6 +933,9 @@ export function registerVariableCommands() {
<li>
<pre><code class="language-stscript">/setvar key=color green</code></pre>
</li>
<li>
<pre><code class="language-stscript">/setvar key=colors index=3 type=string blue</code></pre>
</li>
</ul>
</div>
`,
@ -1015,6 +1034,14 @@ export function registerVariableCommands() {
new SlashCommandNamedArgument(
'index', 'list index', [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.STRING], false,
),
SlashCommandNamedArgument.fromProps({
name: 'type',
description: 'type of the value when used with index',
forceEnum: true,
enumProvider: commonEnumProviders.types,
isRequired: false,
defaultValue: 'string',
}),
],
unnamedArgumentList: [
new SlashCommandArgument(
@ -1024,6 +1051,7 @@ export function registerVariableCommands() {
helpString: `
<div>
Set a global variable value and pass it down the pipe. The <code>index</code> argument is optional.
To perform a type conversion when using <code>index</code>, use the <code>type</code> argument.
</div>
<div>
<strong>Example:</strong>
@ -1031,6 +1059,9 @@ export function registerVariableCommands() {
<li>
<pre><code class="language-stscript">/setglobalvar key=color green</code></pre>
</li>
<li>
<pre><code class="language-stscript">/setglobalvar key=colors index=3 type=string blue</code></pre>
</li>
</ul>
</div>
`,
@ -1247,16 +1278,16 @@ export function registerVariableCommands() {
}),
new SlashCommandNamedArgument(
'rule', 'comparison rule', [ARGUMENT_TYPE.STRING], true, false, null, [
new SlashCommandEnumValue('gt', 'a > b'),
new SlashCommandEnumValue('gte', 'a >= b'),
new SlashCommandEnumValue('lt', 'a < b'),
new SlashCommandEnumValue('lte', 'a <= b'),
new SlashCommandEnumValue('eq', 'a == b'),
new SlashCommandEnumValue('neq', 'a !== b'),
new SlashCommandEnumValue('not', '!a'),
new SlashCommandEnumValue('in', 'a includes b'),
new SlashCommandEnumValue('nin', 'a not includes b'),
],
new SlashCommandEnumValue('gt', 'a > b'),
new SlashCommandEnumValue('gte', 'a >= b'),
new SlashCommandEnumValue('lt', 'a < b'),
new SlashCommandEnumValue('lte', 'a <= b'),
new SlashCommandEnumValue('eq', 'a == b'),
new SlashCommandEnumValue('neq', 'a !== b'),
new SlashCommandEnumValue('not', '!a'),
new SlashCommandEnumValue('in', 'a includes b'),
new SlashCommandEnumValue('nin', 'a not includes b'),
],
),
new SlashCommandNamedArgument(
'else', 'command to execute if not true', [ARGUMENT_TYPE.CLOSURE, ARGUMENT_TYPE.SUBCOMMAND], false,
@ -1325,16 +1356,16 @@ export function registerVariableCommands() {
}),
new SlashCommandNamedArgument(
'rule', 'comparison rule', [ARGUMENT_TYPE.STRING], true, false, null, [
new SlashCommandEnumValue('gt', 'a > b'),
new SlashCommandEnumValue('gte', 'a >= b'),
new SlashCommandEnumValue('lt', 'a < b'),
new SlashCommandEnumValue('lte', 'a <= b'),
new SlashCommandEnumValue('eq', 'a == b'),
new SlashCommandEnumValue('neq', 'a !== b'),
new SlashCommandEnumValue('not', '!a'),
new SlashCommandEnumValue('in', 'a includes b'),
new SlashCommandEnumValue('nin', 'a not includes b'),
],
new SlashCommandEnumValue('gt', 'a > b'),
new SlashCommandEnumValue('gte', 'a >= b'),
new SlashCommandEnumValue('lt', 'a < b'),
new SlashCommandEnumValue('lte', 'a <= b'),
new SlashCommandEnumValue('eq', 'a == b'),
new SlashCommandEnumValue('neq', 'a !== b'),
new SlashCommandEnumValue('not', '!a'),
new SlashCommandEnumValue('in', 'a includes b'),
new SlashCommandEnumValue('nin', 'a not includes b'),
],
),
new SlashCommandNamedArgument(
'guard', 'disable loop iteration limit', [ARGUMENT_TYPE.STRING], false, false, null, commonEnumProviders.boolean('onOff')(),
@ -2030,6 +2061,14 @@ export function registerVariableCommands() {
false, // isRequired
false, // acceptsMultiple
),
SlashCommandNamedArgument.fromProps({
name: 'type',
description: 'type of the value when used with index',
forceEnum: true,
enumProvider: commonEnumProviders.types,
isRequired: false,
defaultValue: 'string',
}),
],
unnamedArgumentList: [
SlashCommandArgument.fromProps({