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: '✔️', true: '✔️',
false: '❌', false: '❌',
null: '🚫',
undefined: '❓',
// Value types // Value types
boolean: '🔲', boolean: '🔲',
@ -230,4 +232,19 @@ export const commonEnumProviders = {
enumTypes.enum, '💉'); 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 { SlashCommandClosure } from './SlashCommandClosure.js';
import { convertValueType } from '../utils.js';
export class SlashCommandScope { export class SlashCommandScope {
/**@type {string[]}*/ variableNames = []; /**@type {string[]}*/ variableNames = [];
@ -55,7 +56,7 @@ export class SlashCommandScope {
if (this.existsVariableInScope(key)) throw new SlashCommandScopeVariableExistsError(`Variable named "${key}" already exists.`); if (this.existsVariableInScope(key)) throw new SlashCommandScopeVariableExistsError(`Variable named "${key}" already exists.`);
this.variables[key] = value; this.variables[key] = value;
} }
setVariable(key, value, index = null) { setVariable(key, value, index = null, type = null) {
if (this.existsVariableInScope(key)) { if (this.existsVariableInScope(key)) {
if (index !== null && index !== undefined) { if (index !== null && index !== undefined) {
let v = this.variables[key]; let v = this.variables[key];
@ -63,13 +64,13 @@ export class SlashCommandScope {
v = JSON.parse(v); v = JSON.parse(v);
const numIndex = Number(index); const numIndex = Number(index);
if (Number.isNaN(numIndex)) { if (Number.isNaN(numIndex)) {
v[index] = value; v[index] = convertValueType(value, type);
} else { } else {
v[numIndex] = value; v[numIndex] = convertValueType(value, type);
} }
v = JSON.stringify(v); v = JSON.stringify(v);
} catch { } catch {
v[index] = value; v[index] = convertValueType(value, type);
} }
this.variables[key] = v; this.variables[key] = v;
} else { } else {
@ -78,7 +79,7 @@ export class SlashCommandScope {
return value; return value;
} }
if (this.parent) { 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}"`); 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 { collapseNewlines } from './power-user.js';
import { debounce_timeout } from './constants.js'; import { debounce_timeout } from './constants.js';
import { Popup, POPUP_RESULT, POPUP_TYPE } from './popup.js'; import { Popup, POPUP_RESULT, POPUP_TYPE } from './popup.js';
import { SlashCommandClosure } from './slash-commands/SlashCommandClosure.js';
/** /**
* Pagination status string template. * 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. * Parses ranges like 10-20 or 10.
* Range is inclusive. Start must be less than end. * 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 { SlashCommandEnumValue, enumTypes } from './slash-commands/SlashCommandEnumValue.js';
import { PARSER_FLAG, SlashCommandParser } from './slash-commands/SlashCommandParser.js'; import { PARSER_FLAG, SlashCommandParser } from './slash-commands/SlashCommandParser.js';
import { SlashCommandScope } from './slash-commands/SlashCommandScope.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/SlashCommandParser.js').NamedArguments} NamedArguments */
/** @typedef {import('./slash-commands/SlashCommand.js').UnnamedArguments} UnnamedArguments */ /** @typedef {import('./slash-commands/SlashCommand.js').UnnamedArguments} UnnamedArguments */
@ -57,12 +57,12 @@ function setLocalVariable(name, value, args = {}) {
if (localVariable === null) { if (localVariable === null) {
localVariable = {}; localVariable = {};
} }
localVariable[args.index] = value; localVariable[args.index] = convertValueType(value, args.as);
} else { } else {
if (localVariable === null) { if (localVariable === null) {
localVariable = []; localVariable = [];
} }
localVariable[numIndex] = value; localVariable[numIndex] = convertValueType(value, args.as);
} }
chat_metadata.variables[name] = JSON.stringify(localVariable); chat_metadata.variables[name] = JSON.stringify(localVariable);
} catch { } catch {
@ -106,12 +106,12 @@ function setGlobalVariable(name, value, args = {}) {
if (globalVariable === null) { if (globalVariable === null) {
globalVariable = {}; globalVariable = {};
} }
globalVariable[args.index] = value; globalVariable[args.index] = convertValueType(value, args.as);
} else { } else {
if (globalVariable === null) { if (globalVariable === null) {
globalVariable = []; globalVariable = [];
} }
globalVariable[numIndex] = value; globalVariable[numIndex] = convertValueType(value, args.as);
} }
extension_settings.variables.global[name] = JSON.stringify(globalVariable); extension_settings.variables.global[name] = JSON.stringify(globalVariable);
} catch { } catch {
@ -667,6 +667,7 @@ function parseNumericSeries(value, scope = null) {
} }
function performOperation(value, operation, singleOperand = false, scope = null) { function performOperation(value, operation, singleOperand = false, scope = null) {
function getResult() {
if (!value) { if (!value) {
return 0; return 0;
} }
@ -686,6 +687,10 @@ function performOperation(value, operation, singleOperand = false, scope = null)
return result; return result;
} }
const result = getResult();
return String(result);
}
function addValuesCallback(args, value) { function addValuesCallback(args, value) {
return performOperation(value, (array) => array.reduce((a, b) => a + b, 0), false, args._scope); return performOperation(value, (array) => array.reduce((a, b) => a + b, 0), false, args._scope);
} }
@ -836,7 +841,7 @@ function varCallback(args, value) {
if (typeof key != 'string') throw new Error('Key must be a string'); if (typeof key != 'string') throw new Error('Key must be a string');
if (args._hasUnnamedArgument) { if (args._hasUnnamedArgument) {
const val = typeof value[0] == 'string' ? value.join(' ') : value[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; return val;
} else { } else {
return args._scope.getVariable(key, args.index); 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 (typeof key != 'string') throw new Error('Key must be a string');
if (value.length > 0) { if (value.length > 0) {
const val = typeof value[0] == 'string' ? value.join(' ') : value[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; return val;
} else { } else {
return args._scope.getVariable(key, args.index); return args._scope.getVariable(key, args.index);
@ -901,6 +906,14 @@ export function registerVariableCommands() {
new SlashCommandNamedArgument( new SlashCommandNamedArgument(
'index', 'list index', [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.STRING], false, '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: [ unnamedArgumentList: [
new SlashCommandArgument( new SlashCommandArgument(
@ -910,6 +923,7 @@ export function registerVariableCommands() {
helpString: ` helpString: `
<div> <div>
Set a local variable value and pass it down the pipe. The <code>index</code> argument is optional. 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>
<div> <div>
<strong>Example:</strong> <strong>Example:</strong>
@ -917,6 +931,9 @@ export function registerVariableCommands() {
<li> <li>
<pre><code class="language-stscript">/setvar key=color green</code></pre> <pre><code class="language-stscript">/setvar key=color green</code></pre>
</li> </li>
<li>
<pre><code class="language-stscript">/setvar key=ages index=John as=number 21</code></pre>
</li>
</ul> </ul>
</div> </div>
`, `,
@ -1015,6 +1032,14 @@ export function registerVariableCommands() {
new SlashCommandNamedArgument( new SlashCommandNamedArgument(
'index', 'list index', [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.STRING], false, '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: [ unnamedArgumentList: [
new SlashCommandArgument( new SlashCommandArgument(
@ -1024,6 +1049,7 @@ export function registerVariableCommands() {
helpString: ` helpString: `
<div> <div>
Set a global variable value and pass it down the pipe. The <code>index</code> argument is optional. 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>
<div> <div>
<strong>Example:</strong> <strong>Example:</strong>
@ -1031,6 +1057,9 @@ export function registerVariableCommands() {
<li> <li>
<pre><code class="language-stscript">/setglobalvar key=color green</code></pre> <pre><code class="language-stscript">/setglobalvar key=color green</code></pre>
</li> </li>
<li>
<pre><code class="language-stscript">/setglobalvar key=ages index=John as=number 21</code></pre>
</li>
</ul> </ul>
</div> </div>
`, `,
@ -2030,6 +2059,14 @@ export function registerVariableCommands() {
false, // isRequired false, // isRequired
false, // acceptsMultiple 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: [ unnamedArgumentList: [
SlashCommandArgument.fromProps({ SlashCommandArgument.fromProps({
@ -2049,7 +2086,8 @@ export function registerVariableCommands() {
splitUnnamedArgumentCount: 1, splitUnnamedArgumentCount: 1,
helpString: ` helpString: `
<div> <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>
<div> <div>
<strong>Examples:</strong> <strong>Examples:</strong>
@ -2060,6 +2098,9 @@ export function registerVariableCommands() {
<li> <li>
<pre><code class="language-stscript">/let x foo | /var key=x foo bar | /var x | /echo</code></pre> <pre><code class="language-stscript">/let x foo | /var key=x foo bar | /var x | /echo</code></pre>
</li> </li>
<li>
<pre><code class="language-stscript">/let x {} | /var index=cool as=number x 1337 | /echo {{var::x}}</code></pre>
</li>
</ul> </ul>
</div> </div>
`, `,