import { isTypedArray as isTypedArrayLodash } from 'lodash'; import type { Data, NdArray, TypedArray } from 'ndarray'; import type { ReactChild, ReactElement } from 'react'; import { EntityKind, DTypeClass } from './models-hdf5'; import type { Entity, Group, GroupWithChildren, Dataset, Datatype, Shape, ScalarShape, ArrayShape, DType, NumericType, H5WebComplex, ComplexArray, BooleanType, ComplexType, PrintableType, StringType, Primitive, Value, CompoundType, PrintableCompoundType, } from './models-hdf5'; import type { AnyNumArray, NumArray } from './models-vis'; import { ScaleType } from './models-vis'; import { getValues } from './utils'; const PRINTABLE_DTYPES = new Set([ DTypeClass.Unsigned, DTypeClass.Integer, DTypeClass.Float, DTypeClass.String, DTypeClass.Bool, DTypeClass.Complex, ]); export function isAbsolutePath(path: string) { return path.startsWith('/'); } export function assertAbsolutePath(path: string) { if (!isAbsolutePath(path)) { throw new Error("Expected path to start with '/'"); } } export function isReactElement(child: ReactChild): child is ReactElement { return typeof child !== 'string' && typeof child !== 'number'; } export function isDefined(val: T): val is T extends undefined ? never : T { return val !== undefined; } export function isNonNull(val: T): val is T extends null ? never : T { return val !== null; } export function assertDefined( val: T, message = 'Expected some value' ): asserts val is T extends undefined ? never : T { if (!isDefined(val)) { throw new TypeError(message); } } export function assertNonNull( val: T, message = 'Expected value to not be null' ): asserts val is T extends null ? never : T { if (!isNonNull(val)) { throw new TypeError(message); } } function assertNum(val: unknown): asserts val is number { if (typeof val !== 'number') { throw new TypeError('Expected number'); } } function assertBool(val: unknown): asserts val is boolean { if (typeof val !== 'boolean') { throw new TypeError('Expected boolean'); } } export function assertStr( val: unknown, message = 'Expected string' ): asserts val is string { if (typeof val !== 'string') { throw new TypeError(message); } } export function assertEnvVar( val: unknown, name: string ): asserts val is string { assertStr(val, `Expected environment variable ${name} to be defined`); if (val === '') { throw new Error(`Expected environment variable ${name} to not be empty`); } } function assertComplex(val: unknown): asserts val is H5WebComplex { if ( !Array.isArray(val) || val.length !== 2 || typeof val[0] !== 'number' || typeof val[1] !== 'number' ) { throw new TypeError('Expected complex'); } } export function assertArray(val: unknown): asserts val is T[] { if (!Array.isArray(val)) { throw new TypeError('Expected array'); } } export function assertArrayOrTypedArray( val: unknown ): asserts val is T[] | TypedArray { if (!Array.isArray(val) && !isTypedArrayLodash(val)) { throw new TypeError('Expected array or typed array'); } } export function isGroup(entity: Entity): entity is Group { return entity.kind === EntityKind.Group; } export function assertGroup(entity: Entity): asserts entity is Group { if (!isGroup(entity)) { throw new Error('Expected group'); } } export function hasChildren(group: Group): group is GroupWithChildren { return 'children' in group; } export function assertGroupWithChildren( group: Group ): asserts group is GroupWithChildren { if (!hasChildren(group)) { throw new Error('Expected group with children'); } } export function isDataset(entity: Entity): entity is Dataset { return entity.kind === EntityKind.Dataset; } export function assertDataset( entity: Entity, message = 'Expected dataset' ): asserts entity is Dataset { if (!isDataset(entity)) { throw new Error(message); } } export function isDatatype(entity: Entity): entity is Datatype { return entity.kind === EntityKind.Datatype; } export function isH5WebComplex( complex: H5WebComplex | ComplexArray ): complex is H5WebComplex { return typeof complex[0] === 'number'; } export function isScalarShape(shape: Shape): shape is ScalarShape { return isNonNull(shape) && shape.length === 0; } export function hasScalarShape( dataset: Dataset ): dataset is Dataset { return isScalarShape(dataset.shape); } export function assertScalarShape( dataset: Dataset ): asserts dataset is Dataset { if (!hasScalarShape(dataset)) { throw new Error('Expected dataset to have scalar shape'); } } export function hasArrayShape( dataset: Dataset ): dataset is Dataset { return isNonNull(dataset.shape) && dataset.shape.length > 0; } export function assertArrayShape( dataset: Dataset ): asserts dataset is Dataset { if (!hasArrayShape(dataset)) { throw new Error('Expected dataset to have array shape'); } } export function hasNonNullShape( dataset: Dataset ): dataset is Dataset { return isNonNull(dataset.shape); } export function assertNonNullShape( dataset: Dataset ): asserts dataset is Dataset { if (!hasNonNullShape(dataset)) { throw new Error('Expected dataset to have non-null shape'); } } export function hasMinDims(dataset: Dataset, min: number): boolean { return dataset.shape.length >= min; } export function assertMinDims(dataset: Dataset, min: number) { if (!hasMinDims(dataset, min)) { throw new Error(`Expected dataset with at least ${min} dimensions`); } } export function hasNumDims(dataset: Dataset, num: number): boolean { return dataset.shape.length === num; } export function assertNumDims(dataset: Dataset, num: number) { if (!hasNumDims(dataset, num)) { throw new Error(`Expected dataset with ${num} dimensions`); } } export function hasBoolType( dataset: Dataset ): dataset is Dataset { return dataset.type.class === DTypeClass.Bool; } function hasStringType( dataset: Dataset ): dataset is Dataset { return dataset.type.class === DTypeClass.String; } export function assertStringType( dataset: Dataset ): asserts dataset is Dataset { if (!hasStringType(dataset)) { throw new Error('Expected dataset to have string type'); } } export function isNumericType(type: DType): type is NumericType { return [DTypeClass.Integer, DTypeClass.Unsigned, DTypeClass.Float].includes( type.class ); } export function hasNumericType( dataset: Dataset ): dataset is Dataset { return isNumericType(dataset.type); } export function assertNumericType( dataset: Dataset ): asserts dataset is Dataset { if (!hasNumericType(dataset)) { throw new Error('Expected dataset to have numeric type'); } } export function isStringType(type: DType): type is StringType { return type.class === DTypeClass.String; } export function isComplexType(type: DType): type is ComplexType { return type.class === DTypeClass.Complex; } export function hasComplexType( dataset: Dataset ): dataset is Dataset { return isComplexType(dataset.type); } export function assertComplexType( dataset: Dataset ): asserts dataset is Dataset { if (!hasComplexType(dataset)) { throw new Error('Expected dataset to have complex type'); } } export function assertNumericOrComplexType( dataset: Dataset ): asserts dataset is Dataset { if (!hasNumericType(dataset) && !hasComplexType(dataset)) { throw new Error('Expected dataset to have numeric or complex type'); } } export function hasPrintableType( entity: Dataset ): entity is Dataset { return PRINTABLE_DTYPES.has(entity.type.class); } export function assertPrintableType( dataset: Dataset ): asserts dataset is Dataset { if ( !hasStringType(dataset) && !hasNumericType(dataset) && !hasBoolType(dataset) && !hasComplexType(dataset) ) { throw new Error('Expected dataset to have displayable type'); } } export function hasCompoundType( dataset: Dataset ): dataset is Dataset { return dataset.type.class === DTypeClass.Compound; } export function assertCompoundType( dataset: Dataset ): asserts dataset is Dataset { if (!hasCompoundType(dataset)) { throw new Error('Expected dataset to have compound type'); } } export function hasPrintableCompoundType( dataset: Dataset ): dataset is Dataset { const { fields } = dataset.type; return Object.values(fields).every((f) => PRINTABLE_DTYPES.has(f.class)); } export function assertPrintableCompoundType( dataset: Dataset ): asserts dataset is Dataset { if (!hasPrintableCompoundType(dataset)) { throw new Error('Expected compound dataset to have printable types'); } } export function isComplexValue( type: DType, value: unknown ): value is H5WebComplex | ComplexArray { return type.class === DTypeClass.Complex; } function assertPrimitiveValue>( dataset: D, value: unknown ): asserts value is Primitive { if (hasNumericType(dataset)) { assertNum(value); } else if (hasStringType(dataset)) { assertStr(value); } else if (hasBoolType(dataset)) { assertBool(value); } else if (hasComplexType(dataset)) { assertComplex(value); } } export function assertDatasetValue>( value: unknown, dataset: D ): asserts value is Value { if (hasArrayShape(dataset)) { assertArrayOrTypedArray(value); if (value.length > 0) { assertPrimitiveValue(dataset, value[0]); } } else { // Scalar shape assertPrimitiveValue(dataset, value); } } export function assertLength( arr: AnyNumArray | undefined, dataLength: number, arrName: string ) { if (!arr) { return; } const { length: arrLength } = getValues(arr); if (arrLength !== dataLength) { throw new Error( `Expected ${arrName} array to have length ${dataLength} instead of ${arrLength}` ); } } export function isScaleType(val: unknown): val is ScaleType { return ( typeof val === 'string' && Object.values(ScaleType).includes(val) ); } export function isNdArray( arr: NdArray | T ): arr is NdArray { return 'data' in arr; } export function isTypedArray(arr: U | T[]): arr is U { return !Array.isArray(arr); } export function isTypedNdArray( ndArr: NdArray ): ndArr is NdArray> { return ndArr.dtype !== 'array'; }