mirror of
https://github.com/tiqi-group/pydase.git
synced 2025-12-19 04:31:19 +01:00
176 lines
5.9 KiB
TypeScript
176 lines
5.9 KiB
TypeScript
import { SerializedObject } from "../types/SerializedObject";
|
|
|
|
export interface State {
|
|
type: string;
|
|
name: string;
|
|
value: Record<string, SerializedObject> | null;
|
|
readonly: boolean;
|
|
doc: string | null;
|
|
}
|
|
|
|
/**
|
|
* Splits a full access path into its atomic parts, separating attribute names, numeric
|
|
* indices (including floating points), and string keys within indices.
|
|
*
|
|
* @param path The full access path string to be split into components.
|
|
* @returns An array of components that make up the path, including attribute names,
|
|
* numeric indices, and string keys as separate elements.
|
|
*/
|
|
export function parseFullAccessPath(path: string): string[] {
|
|
// The pattern matches:
|
|
// \w+ - Words
|
|
// \[\d+\.\d+\] - Floating point numbers inside brackets
|
|
// \[\d+\] - Integers inside brackets
|
|
// \["[^"]*"\] - Double-quoted strings inside brackets
|
|
// \['[^']*'\] - Single-quoted strings inside brackets
|
|
const pattern = /\w+|\[\d+\.\d+\]|\[\d+\]|\["[^"]*"\]|\['[^']*'\]/g;
|
|
const matches = path.match(pattern);
|
|
|
|
return matches ?? []; // Return an empty array if no matches found
|
|
}
|
|
|
|
/**
|
|
* Parse a serialized key and convert it to an appropriate type (number or string).
|
|
*
|
|
* @param serializedKey The serialized key, which might be enclosed in brackets and quotes.
|
|
* @returns The processed key as a number or an unquoted string.
|
|
*
|
|
* Examples:
|
|
* console.log(parseSerializedKey("attr_name")); // Outputs: attr_name (string)
|
|
* console.log(parseSerializedKey("[123]")); // Outputs: 123 (number)
|
|
* console.log(parseSerializedKey("[12.3]")); // Outputs: 12.3 (number)
|
|
* console.log(parseSerializedKey("['hello']")); // Outputs: hello (string)
|
|
* console.log(parseSerializedKey('["12.34"]')); // Outputs: "12.34" (string)
|
|
* console.log(parseSerializedKey('["complex"]'));// Outputs: "complex" (string)
|
|
*/
|
|
function parseSerializedKey(serializedKey: string): string | number {
|
|
// Strip outer brackets if present
|
|
if (serializedKey.startsWith("[") && serializedKey.endsWith("]")) {
|
|
serializedKey = serializedKey.slice(1, -1);
|
|
}
|
|
|
|
// Strip quotes if the resulting string is quoted
|
|
if (
|
|
(serializedKey.startsWith("'") && serializedKey.endsWith("'")) ||
|
|
(serializedKey.startsWith('"') && serializedKey.endsWith('"'))
|
|
) {
|
|
return serializedKey.slice(1, -1);
|
|
}
|
|
|
|
// Try converting to a number if the string is not quoted
|
|
const parsedNumber = parseFloat(serializedKey);
|
|
if (!isNaN(parsedNumber)) {
|
|
return parsedNumber;
|
|
}
|
|
|
|
// Return the original string if it's not a valid number
|
|
return serializedKey;
|
|
}
|
|
|
|
function getOrCreateItemInContainer(
|
|
container: Record<string | number, SerializedObject> | SerializedObject[],
|
|
key: string | number,
|
|
allowAddKey: boolean,
|
|
): SerializedObject {
|
|
// Check if the key exists and return the item if it does
|
|
if (key in container) {
|
|
/* @ts-expect-error Key is in the correct form but converted to type any for some reason */
|
|
return container[key];
|
|
}
|
|
|
|
// Handling the case where the key does not exist
|
|
if (Array.isArray(container)) {
|
|
// Handling arrays
|
|
if (allowAddKey && key === container.length) {
|
|
container.push(createEmptySerializedObject());
|
|
return container[key];
|
|
}
|
|
throw new Error(`Index out of bounds: ${key}`);
|
|
} else {
|
|
// Handling objects
|
|
if (allowAddKey) {
|
|
container[key] = createEmptySerializedObject();
|
|
return container[key];
|
|
}
|
|
throw new Error(`Key not found: ${key}`);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Retrieve an item from a container specified by the passed key. Add an item to the
|
|
* container if allowAppend is set to True.
|
|
*
|
|
* @param container Either a dictionary or list of serialized objects.
|
|
* @param key The key name or index (as a string) representing the attribute in the container.
|
|
* @param allowAppend Whether to allow appending a new entry if the specified index is out of range by exactly one position.
|
|
* @returns The serialized object corresponding to the specified key.
|
|
* @throws SerializationPathError If the key is invalid or leads to an access error without append permissions.
|
|
* @throws SerializationValueError If the expected structure is incorrect.
|
|
*/
|
|
function getContainerItemByKey(
|
|
container: Record<string, SerializedObject> | SerializedObject[],
|
|
key: string,
|
|
allowAppend = false,
|
|
): SerializedObject {
|
|
const processedKey = parseSerializedKey(key);
|
|
|
|
try {
|
|
return getOrCreateItemInContainer(container, processedKey, allowAppend);
|
|
} catch (error) {
|
|
if (error instanceof RangeError) {
|
|
throw new Error(`Index '${processedKey}': ${error.message}`);
|
|
} else if (error instanceof Error) {
|
|
throw new Error(`Key '${processedKey}': ${error.message}`);
|
|
}
|
|
throw error; // Re-throw if it's not a known error type
|
|
}
|
|
}
|
|
|
|
export function setNestedValueByPath(
|
|
serializationDict: Record<string, SerializedObject>,
|
|
path: string,
|
|
serializedValue: SerializedObject,
|
|
): Record<string, SerializedObject> {
|
|
const pathParts = parseFullAccessPath(path);
|
|
const newSerializationDict: Record<string, SerializedObject> = JSON.parse(
|
|
JSON.stringify(serializationDict),
|
|
);
|
|
|
|
let currentDict = newSerializationDict;
|
|
|
|
try {
|
|
for (let i = 0; i < pathParts.length - 1; i++) {
|
|
const pathPart = pathParts[i];
|
|
const nextLevelSerializedObject = getContainerItemByKey(
|
|
currentDict,
|
|
pathPart,
|
|
false,
|
|
);
|
|
currentDict = nextLevelSerializedObject["value"] as Record<
|
|
string,
|
|
SerializedObject
|
|
>;
|
|
}
|
|
|
|
const finalPart = pathParts[pathParts.length - 1];
|
|
const finalObject = getContainerItemByKey(currentDict, finalPart, true);
|
|
|
|
Object.assign(finalObject, serializedValue);
|
|
|
|
return newSerializationDict;
|
|
} catch (error) {
|
|
console.error(`Error occurred trying to change ${path}: ${error}`);
|
|
}
|
|
return {};
|
|
}
|
|
|
|
function createEmptySerializedObject(): SerializedObject {
|
|
return {
|
|
full_access_path: "",
|
|
value: null,
|
|
type: "None",
|
|
doc: null,
|
|
readonly: false,
|
|
};
|
|
}
|