Merge pull request #2731 from SillyTavern/fix-pipe-types

Add type conversion for /setvar commands with index
This commit is contained in:
Cohee
2024-09-01 23:26:46 +03:00
committed by GitHub
4 changed files with 156 additions and 28 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,7 @@ 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) {
if (this.existsVariableInScope(key)) {
if (index !== null && index !== undefined) {
let v = this.variables[key];
@ -63,13 +64,13 @@ export class SlashCommandScope {
v = JSON.parse(v);
const numIndex = Number(index);
if (Number.isNaN(numIndex)) {
v[index] = value;
v[index] = convertValueType(value, type);
} else {
v[numIndex] = value;
v[numIndex] = convertValueType(value, type);
}
v = JSON.stringify(v);
} catch {
v[index] = value;
v[index] = convertValueType(value, type);
}
this.variables[key] = v;
} else {
@ -78,7 +79,7 @@ export class SlashCommandScope {
return value;
}
if (this.parent) {
return this.parent.setVariable(key, value, index);
return this.parent.setVariable(key, value, index, type);
}
throw new SlashCommandScopeVariableNotFoundError(`No such variable: "${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;
}
switch (type.trim().toLowerCase()) {
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;
}
// The value is not an array
return [];
} catch {
return [];
}
case 'object':
case 'dict':
case 'dictionary':
try {
const parsedObject = JSON.parse(value);
if (typeof parsedObject === 'object') {
return parsedObject;
}
// The value is not an object
return {};
} 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 */
@ -57,12 +57,12 @@ function setLocalVariable(name, value, args = {}) {
if (localVariable === null) {
localVariable = {};
}
localVariable[args.index] = value;
localVariable[args.index] = convertValueType(value, args.as);
} else {
if (localVariable === null) {
localVariable = [];
}
localVariable[numIndex] = value;
localVariable[numIndex] = convertValueType(value, args.as);
}
chat_metadata.variables[name] = JSON.stringify(localVariable);
} catch {
@ -106,12 +106,12 @@ function setGlobalVariable(name, value, args = {}) {
if (globalVariable === null) {
globalVariable = {};
}
globalVariable[args.index] = value;
globalVariable[args.index] = convertValueType(value, args.as);
} else {
if (globalVariable === null) {
globalVariable = [];
}
globalVariable[numIndex] = value;
globalVariable[numIndex] = convertValueType(value, args.as);
}
extension_settings.variables.global[name] = JSON.stringify(globalVariable);
} catch {
@ -667,6 +667,7 @@ function parseNumericSeries(value, scope = null) {
}
function performOperation(value, operation, singleOperand = false, scope = null) {
function getResult() {
if (!value) {
return 0;
}
@ -684,6 +685,10 @@ function performOperation(value, operation, singleOperand = false, scope = null)
}
return result;
}
const result = getResult();
return String(result);
}
function addValuesCallback(args, value) {
@ -836,7 +841,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.as);
return val;
} else {
return args._scope.getVariable(key, args.index);
@ -846,7 +851,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.as);
return val;
} else {
return args._scope.getVariable(key, args.index);
@ -901,6 +906,14 @@ export function registerVariableCommands() {
new SlashCommandNamedArgument(
'index', 'list index', [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.STRING], false,
),
SlashCommandNamedArgument.fromProps({
name: 'as',
description: 'change the type of the value when used with index',
forceEnum: true,
enumProvider: commonEnumProviders.types,
isRequired: false,
defaultValue: 'string',
}),
],
unnamedArgumentList: [
new SlashCommandArgument(
@ -910,6 +923,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 convert the value to a specific JSON type when using <code>index</code>, use the <code>as</code> argument.
</div>
<div>
<strong>Example:</strong>
@ -917,6 +931,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=ages index=John as=number 21</code></pre>
</li>
</ul>
</div>
`,
@ -1015,6 +1032,14 @@ export function registerVariableCommands() {
new SlashCommandNamedArgument(
'index', 'list index', [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.STRING], false,
),
SlashCommandNamedArgument.fromProps({
name: 'as',
description: 'change the type of the value when used with index',
forceEnum: true,
enumProvider: commonEnumProviders.types,
isRequired: false,
defaultValue: 'string',
}),
],
unnamedArgumentList: [
new SlashCommandArgument(
@ -1024,6 +1049,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 convert the value to a specific JSON type when using <code>index</code>, use the <code>as</code> argument.
</div>
<div>
<strong>Example:</strong>
@ -1031,6 +1057,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=ages index=John as=number 21</code></pre>
</li>
</ul>
</div>
`,
@ -2030,6 +2059,14 @@ export function registerVariableCommands() {
false, // isRequired
false, // acceptsMultiple
),
SlashCommandNamedArgument.fromProps({
name: 'as',
description: 'change the type of the value when used with index',
forceEnum: true,
enumProvider: commonEnumProviders.types,
isRequired: false,
defaultValue: 'string',
}),
],
unnamedArgumentList: [
SlashCommandArgument.fromProps({
@ -2049,7 +2086,8 @@ export function registerVariableCommands() {
splitUnnamedArgumentCount: 1,
helpString: `
<div>
Get or set a variable.
Get or set a variable. Use <code>index</code> to access elements of a JSON-serialized list or dictionary.
To convert the value to a specific JSON type when using with <code>index</code>, use the <code>as</code> argument.
</div>
<div>
<strong>Examples:</strong>
@ -2060,6 +2098,9 @@ export function registerVariableCommands() {
<li>
<pre><code class="language-stscript">/let x foo | /var key=x foo bar | /var x | /echo</code></pre>
</li>
<li>
<pre><code class="language-stscript">/let x {} | /var index=cool as=number x 1337 | /echo {{var::x}}</code></pre>
</li>
</ul>
</div>
`,