Add type conversion for /setvar commands with index

3746f08590ea623656fb4eecbdf802be7c59c10a

Cohee <18619528+Cohee1207@users.noreply.github.com>

4 files changed, +131 -4Showing whitespace changes
public/scripts/slash-commands/SlashCommandCommonEnumsProvider.js+17 -0
@@ -36,6 +36,8 @@ export const enumIcons = {
3636
37 true: '✔️',37 true: '✔️',
38 false: '❌',38 false: '❌',
39 null: '🚫',
40 undefined: '❓',
3941
40 // Value types42 // Value types
41 boolean: '🔲',43 boolean: '🔲',
@@ -230,4 +232,19 @@ export const commonEnumProviders = {
230 enumTypes.enum, '💉');232 enumTypes.enum, '💉');
231 });233 });
232 },234 },
235
236 /**
237 * Gets somewhat recognizable STscript types.
238 *
239 * @returns {SlashCommandEnumValue[]}
240 */
241 types: () => [
242 new SlashCommandEnumValue('string', null, enumTypes.type, enumIcons.string),
243 new SlashCommandEnumValue('number', null, enumTypes.type, enumIcons.number),
244 new SlashCommandEnumValue('boolean', null, enumTypes.type, enumIcons.boolean),
245 new SlashCommandEnumValue('array', null, enumTypes.type, enumIcons.array),
246 new SlashCommandEnumValue('object', null, enumTypes.type, enumIcons.dictionary),
247 new SlashCommandEnumValue('null', null, enumTypes.type, enumIcons.null),
248 new SlashCommandEnumValue('undefined', null, enumTypes.type, enumIcons.undefined),
249 ],
233};250};
public/scripts/slash-commands/SlashCommandScope.js+3 -1
@@ -1,4 +1,5 @@
1import { SlashCommandClosure } from './SlashCommandClosure.js';1import { SlashCommandClosure } from './SlashCommandClosure.js';
2import { convertValueType } from '../utils.js';
23
3export class SlashCommandScope {4export class SlashCommandScope {
4 /**@type {string[]}*/ variableNames = [];5 /**@type {string[]}*/ variableNames = [];
@@ -55,7 +56,8 @@ export class SlashCommandScope {
55 if (this.existsVariableInScope(key)) throw new SlashCommandScopeVariableExistsError(`Variable named "${key}" already exists.`);56 if (this.existsVariableInScope(key)) throw new SlashCommandScopeVariableExistsError(`Variable named "${key}" already exists.`);
56 this.variables[key] = value;57 this.variables[key] = value;
57 }58 }
58 setVariable(key, value, index = null) {59 setVariable(key, value, index = null, type = null) {
60 value = convertValueType(value, type);
59 if (this.existsVariableInScope(key)) {61 if (this.existsVariableInScope(key)) {
60 if (index !== null && index !== undefined) {62 if (index !== null && index !== undefined) {
61 let v = this.variables[key];63 let v = this.variables[key];
public/scripts/utils.js+69 -0
@@ -4,6 +4,7 @@ import { isMobile } from './RossAscends-mods.js';
4import { collapseNewlines } from './power-user.js';4import { collapseNewlines } from './power-user.js';
5import { debounce_timeout } from './constants.js';5import { debounce_timeout } from './constants.js';
6import { Popup, POPUP_RESULT, POPUP_TYPE } from './popup.js';6import { Popup, POPUP_RESULT, POPUP_TYPE } from './popup.js';
7import { SlashCommandClosure } from './slash-commands/SlashCommandClosure.js';
78
8/**9/**
9 * Pagination status string template.10 * Pagination status string template.
@@ -34,6 +35,74 @@ export function isValidUrl(value) {
34}35}
3536
36/**37/**
38 * Converts string to a value of a given type. Includes pythonista-friendly aliases.
39 * @param {string|SlashCommandClosure} value String value
40 * @param {string} type Type to convert to
41 * @returns {any} Converted value
42 */
43export function convertValueType(value, type) {
44 if (value instanceof SlashCommandClosure || typeof type !== 'string') {
45 return value;
46 }
47
48 type = type.trim().toLowerCase();
49
50 switch (type) {
51 case 'string':
52 case 'str':
53 return String(value);
54
55 case 'null':
56 return null;
57
58 case 'undefined':
59 case 'none':
60 return undefined;
61
62 case 'number':
63 return Number(value);
64
65 case 'int':
66 return parseInt(value, 10);
67
68 case 'float':
69 return parseFloat(value);
70
71 case 'boolean':
72 case 'bool':
73 return isTrueBoolean(value);
74
75 case 'list':
76 case 'array':
77 try {
78 const parsedArray = JSON.parse(value);
79 if (Array.isArray(parsedArray)) {
80 return parsedArray;
81 }
82 throw new Error('Value is not an array.');
83 } catch {
84 return [];
85 }
86
87 case 'object':
88 case 'dict':
89 case 'dictionary':
90 try {
91 const parsedObject = JSON.parse(value);
92 if (typeof parsedObject === 'object') {
93 return parsedObject;
94 }
95 throw new Error('Value is not an object.');
96 } catch {
97 return {};
98 }
99
100 default:
101 return value;
102 }
103}
104
105/**
37 * Parses ranges like 10-20 or 10.106 * Parses ranges like 10-20 or 10.
38 * Range is inclusive. Start must be less than end.107 * Range is inclusive. Start must be less than end.
39 * Returns null if invalid.108 * Returns null if invalid.
public/scripts/variables.js+42 -3
@@ -11,7 +11,7 @@ import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCom
11import { SlashCommandEnumValue, enumTypes } from './slash-commands/SlashCommandEnumValue.js';11import { SlashCommandEnumValue, enumTypes } from './slash-commands/SlashCommandEnumValue.js';
12import { PARSER_FLAG, SlashCommandParser } from './slash-commands/SlashCommandParser.js';12import { PARSER_FLAG, SlashCommandParser } from './slash-commands/SlashCommandParser.js';
13import { SlashCommandScope } from './slash-commands/SlashCommandScope.js';13import { SlashCommandScope } from './slash-commands/SlashCommandScope.js';
14import { isFalseBoolean } from './utils.js';14import { isFalseBoolean, convertValueType } from './utils.js';
1515
16/** @typedef {import('./slash-commands/SlashCommandParser.js').NamedArguments} NamedArguments */16/** @typedef {import('./slash-commands/SlashCommandParser.js').NamedArguments} NamedArguments */
17/** @typedef {import('./slash-commands/SlashCommand.js').UnnamedArguments} UnnamedArguments */17/** @typedef {import('./slash-commands/SlashCommand.js').UnnamedArguments} UnnamedArguments */
@@ -51,6 +51,7 @@ function setLocalVariable(name, value, args = {}) {
5151
52 if (args.index !== undefined) {52 if (args.index !== undefined) {
53 try {53 try {
54 value = convertValueType(value, args.type);
54 let localVariable = JSON.parse(chat_metadata.variables[name] ?? 'null');55 let localVariable = JSON.parse(chat_metadata.variables[name] ?? 'null');
55 const numIndex = Number(args.index);56 const numIndex = Number(args.index);
56 if (Number.isNaN(numIndex)) {57 if (Number.isNaN(numIndex)) {
@@ -100,6 +101,7 @@ function getGlobalVariable(name, args = {}) {
100function setGlobalVariable(name, value, args = {}) {101function setGlobalVariable(name, value, args = {}) {
101 if (args.index !== undefined) {102 if (args.index !== undefined) {
102 try {103 try {
104 value = convertValueType(value, args.type);
103 let globalVariable = JSON.parse(extension_settings.variables.global[name] ?? 'null');105 let globalVariable = JSON.parse(extension_settings.variables.global[name] ?? 'null');
104 const numIndex = Number(args.index);106 const numIndex = Number(args.index);
105 if (Number.isNaN(numIndex)) {107 if (Number.isNaN(numIndex)) {
@@ -667,6 +669,7 @@ function parseNumericSeries(value, scope = null) {
667}669}
668670
669function performOperation(value, operation, singleOperand = false, scope = null) {671function performOperation(value, operation, singleOperand = false, scope = null) {
672 function getResult() {
670 if (!value) {673 if (!value) {
671 return 0;674 return 0;
672 }675 }
@@ -686,6 +689,10 @@ function performOperation(value, operation, singleOperand = false, scope = null)
686 return result;689 return result;
687 }690 }
688691
692 const result = getResult();
693 return String(result);
694}
695
689function addValuesCallback(args, value) {696function addValuesCallback(args, value) {
690 return performOperation(value, (array) => array.reduce((a, b) => a + b, 0), false, args._scope);697 return performOperation(value, (array) => array.reduce((a, b) => a + b, 0), false, args._scope);
691}698}
@@ -836,7 +843,7 @@ function varCallback(args, value) {
836 if (typeof key != 'string') throw new Error('Key must be a string');843 if (typeof key != 'string') throw new Error('Key must be a string');
837 if (args._hasUnnamedArgument) {844 if (args._hasUnnamedArgument) {
838 const val = typeof value[0] == 'string' ? value.join(' ') : value[0];845 const val = typeof value[0] == 'string' ? value.join(' ') : value[0];
839 args._scope.setVariable(key, val, args.index);846 args._scope.setVariable(key, val, args.index, args.type);
840 return val;847 return val;
841 } else {848 } else {
842 return args._scope.getVariable(key, args.index);849 return args._scope.getVariable(key, args.index);
@@ -846,7 +853,7 @@ function varCallback(args, value) {
846 if (typeof key != 'string') throw new Error('Key must be a string');853 if (typeof key != 'string') throw new Error('Key must be a string');
847 if (value.length > 0) {854 if (value.length > 0) {
848 const val = typeof value[0] == 'string' ? value.join(' ') : value[0];855 const val = typeof value[0] == 'string' ? value.join(' ') : value[0];
849 args._scope.setVariable(key, val, args.index);856 args._scope.setVariable(key, val, args.index, args.type);
850 return val;857 return val;
851 } else {858 } else {
852 return args._scope.getVariable(key, args.index);859 return args._scope.getVariable(key, args.index);
@@ -901,6 +908,14 @@ export function registerVariableCommands() {
901 new SlashCommandNamedArgument(908 new SlashCommandNamedArgument(
902 'index', 'list index', [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.STRING], false,909 'index', 'list index', [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.STRING], false,
903 ),910 ),
911 SlashCommandNamedArgument.fromProps({
912 name: 'type',
913 description: 'type of the value when used with index',
914 forceEnum: true,
915 enumProvider: commonEnumProviders.types,
916 isRequired: false,
917 defaultValue: 'string',
918 }),
904 ],919 ],
905 unnamedArgumentList: [920 unnamedArgumentList: [
906 new SlashCommandArgument(921 new SlashCommandArgument(
@@ -910,6 +925,7 @@ export function registerVariableCommands() {
910 helpString: `925 helpString: `
911 <div>926 <div>
912 Set a local variable value and pass it down the pipe. The <code>index</code> argument is optional.927 Set a local variable value and pass it down the pipe. The <code>index</code> argument is optional.
928 To perform a type conversion when using <code>index</code>, use the <code>type</code> argument.
913 </div>929 </div>
914 <div>930 <div>
915 <strong>Example:</strong>931 <strong>Example:</strong>
@@ -917,6 +933,9 @@ export function registerVariableCommands() {
917 <li>933 <li>
918 <pre><code class="language-stscript">/setvar key=color green</code></pre>934 <pre><code class="language-stscript">/setvar key=color green</code></pre>
919 </li>935 </li>
936 <li>
937 <pre><code class="language-stscript">/setvar key=colors index=3 type=string blue</code></pre>
938 </li>
920 </ul>939 </ul>
921 </div>940 </div>
922 `,941 `,
@@ -1015,6 +1034,14 @@ export function registerVariableCommands() {
1015 new SlashCommandNamedArgument(1034 new SlashCommandNamedArgument(
1016 'index', 'list index', [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.STRING], false,1035 'index', 'list index', [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.STRING], false,
1017 ),1036 ),
1037 SlashCommandNamedArgument.fromProps({
1038 name: 'type',
1039 description: 'type of the value when used with index',
1040 forceEnum: true,
1041 enumProvider: commonEnumProviders.types,
1042 isRequired: false,
1043 defaultValue: 'string',
1044 }),
1018 ],1045 ],
1019 unnamedArgumentList: [1046 unnamedArgumentList: [
1020 new SlashCommandArgument(1047 new SlashCommandArgument(
@@ -1024,6 +1051,7 @@ export function registerVariableCommands() {
1024 helpString: `1051 helpString: `
1025 <div>1052 <div>
1026 Set a global variable value and pass it down the pipe. The <code>index</code> argument is optional.1053 Set a global variable value and pass it down the pipe. The <code>index</code> argument is optional.
1054 To perform a type conversion when using <code>index</code>, use the <code>type</code> argument.
1027 </div>1055 </div>
1028 <div>1056 <div>
1029 <strong>Example:</strong>1057 <strong>Example:</strong>
@@ -1031,6 +1059,9 @@ export function registerVariableCommands() {
1031 <li>1059 <li>
1032 <pre><code class="language-stscript">/setglobalvar key=color green</code></pre>1060 <pre><code class="language-stscript">/setglobalvar key=color green</code></pre>
1033 </li>1061 </li>
1062 <li>
1063 <pre><code class="language-stscript">/setglobalvar key=colors index=3 type=string blue</code></pre>
1064 </li>
1034 </ul>1065 </ul>
1035 </div>1066 </div>
1036 `,1067 `,
@@ -2030,6 +2061,14 @@ export function registerVariableCommands() {
2030 false, // isRequired2061 false, // isRequired
2031 false, // acceptsMultiple2062 false, // acceptsMultiple
2032 ),2063 ),
2064 SlashCommandNamedArgument.fromProps({
2065 name: 'type',
2066 description: 'type of the value when used with index',
2067 forceEnum: true,
2068 enumProvider: commonEnumProviders.types,
2069 isRequired: false,
2070 defaultValue: 'string',
2071 }),
2033 ],2072 ],
2034 unnamedArgumentList: [2073 unnamedArgumentList: [
2035 SlashCommandArgument.fromProps({2074 SlashCommandArgument.fromProps({