@pmouli/isy-matter-server
Version:
Service to expose an ISY device as a Matter Border router
548 lines (516 loc) • 18.3 kB
text/typescript
import { factory } from "typescript";
import ts from "typescript";
import { UnitOfMeasure } from "../Definitions/Global/UOM.js";
import type {
NodeClassDefinition,
DriverDefinition,
ParameterDefinition,
CommandDefinition,
DataTypeDefinition,
} from "../Model/ClassDefinition.js";
import { EnumDefinition, EnumDefinitionMap } from "../Model/EnumDefinition.js";
import { isGeneratorFunction } from "util/types";
import { Logger, loggers } from "winston";
import { pascalCase } from 'moderndash';
import { Family } from '../Definitions/index.js';
const logger = loggers.get("EnumFactory");
export function buildEnums<T extends Family>(map: { [x: string]: EnumDefinition<T> }): GeneratedEnum<T>[] {
let enums = [];
for (const indexId in map) {
enums.push(createEnum(map[indexId]));
}
return enums;
}
type GeneratedEnum<T extends Family> = {
family: T;
name: string;
id: string;
path: string;
statements: ts.EnumDeclaration[];
};
export function createEnum<T extends Family>(enumDef: EnumDefinition<T>) : GeneratedEnum<T> {
try {
const enumNode : ts.EnumDeclaration = factory.createEnumDeclaration(
[factory.createToken(ts.SyntaxKind.ExportKeyword)],
factory.createIdentifier(enumDef.name),
[
...Object.entries(enumDef.values).map(([name, value]) =>
factory.createEnumMember(createMemberName(name), factory.createNumericLiteral(value))
),
]
)
return {
family: enumDef.family,
name: enumDef.name,
path: `/${Family[enumDef.family]}/generated/${enumDef.name}.ts`,
id: enumDef.id,
statements: [enumNode]
};
} catch (e) {
if (logger) logger.error(`Error creating ${Family[enumDef.family]} ${enumDef.name} enum: ${e.message}`, e.stack);
else {
throw e;
}
}
}
class CodeFactory {
}
export class EnumFactory extends CodeFactory {
static generateEnumsForFamily<T extends Family>(family: T): GeneratedEnum<T>[] {
return buildEnums<T>(EnumDefinitionMap.get(family) as { [x: string]: EnumDefinition<T> });
}
static generateAll() {
let t = [];
for (const key of EnumDefinitionMap.keys()) {
try {
let e = this.generateEnumsForFamily(key);
t.push(...e);
t.push(buildEnumIndex(key, e));
} catch (e) {
if (logger) logger.error(`Error generating enums for ${Family[key]}: ${e.message}`, e.stack);
else {
throw e;
}
}
}
return t
}
}
function buildEnumIndex<T extends Family>(family: T, enums: GeneratedEnum<T>[]) {
return {
family,
path: `/${Family[family]}/generated/index.ts`,
statements: [
...enums.map((p) =>
factory.createExportDeclaration(
undefined,
false,
undefined,
factory.createStringLiteral(`./${p.name}.js`),
undefined
)
),
],
};
}
function createDriverInitializationStatement(def: DriverDefinition): ts.Statement {
return factory.createExpressionStatement(
factory.createBinaryExpression(
factory.createPropertyAccessExpression(
factory.createPropertyAccessExpression(factory.createThis(), factory.createIdentifier("drivers")),
factory.createIdentifier(def.id)
),
factory.createToken(ts.SyntaxKind.EqualsToken),
factory.createCallExpression(
factory.createPropertyAccessExpression(factory.createIdentifier("Driver"), factory.createIdentifier("create")),
undefined,
[
factory.createStringLiteral("ST"),
factory.createThis(),
factory.createAsExpression(
factory.createPropertyAccessExpression(
factory.createIdentifier("nodeInfo"),
factory.createIdentifier("property")
),
factory.createTypeReferenceNode(factory.createIdentifier("DriverState"), undefined)
),
factory.createObjectLiteralExpression(
[
factory.createPropertyAssignment(
factory.createIdentifier("uom"),
factory.createPropertyAccessExpression(
factory.createIdentifier("UnitOfMeasure"),
factory.createIdentifier(UnitOfMeasure[def.dataType[0]?.uom] ?? "Unknown")
)
),
factory.createPropertyAssignment(
factory.createIdentifier("label"),
factory.createStringLiteral(def.label)
),
factory.createPropertyAssignment(factory.createIdentifier("name"), factory.createStringLiteral(def.name)),
],
false
),
]
)
)
);
}
function createDriverSignatureReturnType(def: DataTypeDefinition): ts.TypeNode {
if (def.enum) {
return factory.createUnionTypeNode(
Object.keys(def.values).map((p) => factory.createLiteralTypeNode(factory.createNumericLiteral(p)))
);
} else return factory.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword);
}
function createDriverPropertyReturnType(def: DataTypeDefinition): ts.TypeNode {
if (def.enum) {
{
if (Object.keys(def.values).length == 2) {
return factory.createKeywordTypeNode(ts.SyntaxKind.BooleanKeyword);
}
return factory.createUnionTypeNode(
Object.keys(def.values).map((p) => factory.createLiteralTypeNode(factory.createNumericLiteral(p)))
);
// return factory.createUnionTypeNode(
// Object.values(def.values).map((p) => factory.createTypeReferenceNode(
// factory.createQualifiedName(
// factory.createIdentifier("UnitOfMeasure"),
// factory.createIdentifier(UnitOfMeasure)
// ),
// undefined
// )))
}
} else return factory.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword);
}
function createParameterSignature(def: ParameterDefinition) {
return factory.createParameterDeclaration(
undefined,
undefined,
factory.createIdentifier(def.id ?? "value"),
undefined,
undefined,
undefined
);
}
function createCommandSignature(def: CommandDefinition) {
return factory.createPropertySignature(
undefined,
factory.createIdentifier(def.id),
undefined,
factory.createFunctionTypeNode(
undefined,
def.parameters ? Object.values(def.parameters).map(createParameterSignature) : [],
factory.createTypeReferenceNode(factory.createIdentifier("Promise"), [
factory.createKeywordTypeNode(ts.SyntaxKind.BooleanKeyword),
])
)
);
}
function createDriverSignature(def: DriverDefinition) {
return factory.createPropertySignature(
undefined,
factory.createIdentifier(def.id),
factory.createToken(ts.SyntaxKind.QuestionToken),
factory.createTypeLiteralNode([
factory.createPropertySignature(
undefined,
factory.createIdentifier("uom"),
undefined,
def.dataType
? factory.createUnionTypeNode(Object.values(def.dataType).map((p) => createTypeNodeForUOM(p.uom)))
: factory.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword)
),
factory.createPropertySignature(
undefined,
factory.createIdentifier("value"),
undefined,
def.dataType
? factory.createUnionTypeNode(Object.values(def.dataType).map(createDriverSignatureReturnType))
: factory.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword)
),
])
);
}
function createTypeNodeForUOM(uom: number): ts.TypeNode {
return factory.createTypeReferenceNode(
factory.createQualifiedName(
factory.createIdentifier("UnitOfMeasure"),
factory.createIdentifier(UnitOfMeasure[uom] ?? "Unknown")
)
);
}
function createParameterDeclarationSignature(def: ParameterDefinition) {
return factory.createParameterDeclaration(
undefined,
undefined,
factory.createIdentifier(def.name ?? "value"),
undefined,
undefined,
undefined
);
}
function createCommandMethodDeclaration(def: CommandDefinition) {
return factory.createMethodDeclaration(
[factory.createToken(ts.SyntaxKind.AsyncKeyword)],
undefined,
factory.createIdentifier(def.name),
undefined,
undefined,
def.parameters ? Object.values(def.parameters).map(createParameterDeclarationSignature) : undefined,
undefined,
factory.createBlock(
[
factory.createExpressionStatement(
factory.createCallExpression(
factory.createPropertyAccessExpression(factory.createThis(), factory.createIdentifier("sendCommand")),
undefined,
[
factory.createStringLiteral(def.id),
...(def.parameters
? [
factory.createObjectLiteralExpression(
[
...Object.values(def.parameters).map((p) =>
factory.createPropertyAssignment(
factory.createIdentifier(p.id ?? "value"),
factory.createIdentifier(p.name ?? "value")
)
),
],
false
),
]
: []),
]
)
),
],
true
)
);
}
function createDriverGetDeclaration(def: DriverDefinition) {
return factory.createGetAccessorDeclaration(
[factory.createToken(ts.SyntaxKind.PublicKeyword)],
factory.createIdentifier(def.name),
[],
factory.createUnionTypeNode(Object.values(def.dataType).map(createDriverPropertyReturnType)),
factory.createBlock(
[
factory.createReturnStatement(
factory.createPropertyAccessChain(
factory.createPropertyAccessExpression(
factory.createPropertyAccessExpression(factory.createThis(), factory.createIdentifier("drivers")),
factory.createIdentifier(def.id)
),
factory.createToken(ts.SyntaxKind.QuestionDotToken),
factory.createIdentifier("value")
)
),
],
true
)
);
}
export function createNodeClass<T extends Family>(nodeClassDef: NodeClassDefinition<T>) {
return {
name: nodeClassDef.name,
id: nodeClassDef.id,
statements: [
factory.createImportDeclaration(
undefined,
factory.createImportClause(
false,
undefined,
factory.createNamedImports([
factory.createImportSpecifier(false, undefined, factory.createIdentifier("UnitOfMeasure")),
])
),
factory.createStringLiteral("../../../Definitions/Global/UOM.js"),
undefined
),
factory.createImportDeclaration(
undefined,
factory.createImportClause(
false,
undefined,
factory.createNamedImports([
factory.createImportSpecifier(false, undefined, factory.createIdentifier("Family")),
])
),
factory.createStringLiteral("../../../Definitions/Global/Families.js"),
undefined
),
factory.createImportDeclaration(
undefined,
factory.createImportClause(
true,
undefined,
factory.createNamedImports([
factory.createImportSpecifier(false, undefined, factory.createIdentifier("NodeInfo")),
])
),
factory.createStringLiteral("../../../Definitions/NodeInfo.js"),
undefined
),
factory.createImportDeclaration(
undefined,
factory.createImportClause(
true,
undefined,
factory.createNamedImports([factory.createImportSpecifier(false, undefined, factory.createIdentifier("ISY"))])
),
factory.createStringLiteral("../../../ISY.js"),
undefined
),
factory.createImportDeclaration(
undefined,
factory.createImportClause(
false,
undefined,
factory.createNamedImports([
factory.createImportSpecifier(false, undefined, factory.createIdentifier("ISYDeviceNode")),
])
),
factory.createStringLiteral("../../../ISYNode.js"),
undefined
),
factory.createImportDeclaration(
undefined,
factory.createImportClause(
false,
undefined,
factory.createNamedImports([
factory.createImportSpecifier(false, undefined, factory.createIdentifier("Driver")),
])
),
factory.createStringLiteral("../../../Definitions/Global/Drivers.js"),
undefined
),
factory.createImportDeclaration(
undefined,
factory.createImportClause(
true,
undefined,
factory.createNamedImports([
factory.createImportSpecifier(false, undefined, factory.createIdentifier("DriverState")),
])
),
factory.createStringLiteral("../../../Model/DriverState.js"),
undefined
),
factory.createVariableStatement(
[factory.createToken(ts.SyntaxKind.ExportKeyword)],
factory.createVariableDeclarationList(
[
factory.createVariableDeclaration(
factory.createIdentifier("nodeDefId"),
undefined,
undefined,
factory.createStringLiteral(nodeClassDef.id)
),
],
ts.NodeFlags.Const
)
),
factory.createTypeAliasDeclaration(
undefined,
factory.createIdentifier("Commands"),
undefined,
factory.createTypeLiteralNode([...Object.values(nodeClassDef.commands).map(createCommandSignature)])
),
factory.createTypeAliasDeclaration(
undefined,
factory.createIdentifier("Drivers"),
undefined,
factory.createTypeLiteralNode([...Object.values(nodeClassDef.drivers).map(createDriverSignature)])
),
factory.createClassDeclaration(
[factory.createToken(ts.SyntaxKind.ExportKeyword)],
factory.createIdentifier(nodeClassDef.name),
undefined,
[
factory.createHeritageClause(ts.SyntaxKind.ExtendsKeyword, [
factory.createExpressionWithTypeArguments(factory.createIdentifier("ISYDeviceNode"), [
factory.createTypeReferenceNode(
factory.createQualifiedName(
factory.createIdentifier("Family"),
factory.createIdentifier(Family[nodeClassDef.family])
),
undefined
),
factory.createTypeOperatorNode(
ts.SyntaxKind.KeyOfKeyword,
factory.createTypeReferenceNode(factory.createIdentifier("Drivers"), undefined)
),
factory.createTypeOperatorNode(
ts.SyntaxKind.KeyOfKeyword,
factory.createTypeReferenceNode(factory.createIdentifier("Commands"), undefined)
),
]),
]),
],
[
factory.createPropertyDeclaration(
[factory.createToken(ts.SyntaxKind.PublicKeyword), factory.createToken(ts.SyntaxKind.ReadonlyKeyword)],
factory.createIdentifier("commands"),
undefined,
factory.createTypeReferenceNode(factory.createIdentifier("Commands"), undefined),
factory.createObjectLiteralExpression(
[
...Object.values(nodeClassDef.commands).map((c) =>
factory.createPropertyAssignment(
factory.createIdentifier(c.id),
factory.createPropertyAccessExpression(factory.createThis(), factory.createIdentifier(c.name))
)
),
],
true
)
),
factory.createPropertyDeclaration(
[factory.createToken(ts.SyntaxKind.PublicKeyword)],
factory.createIdentifier("drivers"),
undefined,
factory.createTypeReferenceNode(factory.createIdentifier("Drivers"), undefined),
factory.createObjectLiteralExpression([], false)
),
factory.createPropertyDeclaration(
[factory.createToken(ts.SyntaxKind.StaticKeyword)],
factory.createIdentifier("nodeDefId"),
undefined,
undefined,
factory.createStringLiteral(nodeClassDef.id)
),
factory.createConstructorDeclaration(
undefined,
[
factory.createParameterDeclaration(
undefined,
undefined,
factory.createIdentifier("isy"),
undefined,
factory.createTypeReferenceNode(factory.createIdentifier("ISY"), undefined),
undefined
),
factory.createParameterDeclaration(
undefined,
undefined,
factory.createIdentifier("nodeInfo"),
undefined,
factory.createTypeReferenceNode(factory.createIdentifier("NodeInfo"), undefined),
undefined
),
],
factory.createBlock(
[
factory.createExpressionStatement(
factory.createCallExpression(factory.createSuper(), undefined, [
factory.createIdentifier("isy"),
factory.createIdentifier("nodeInfo"),
])
),
...Object.values(nodeClassDef.drivers).map(createDriverInitializationStatement),
],
true
)
),
...Object.values(nodeClassDef.commands).map(createCommandMethodDeclaration),
...Object.values(nodeClassDef.drivers).map(createDriverGetDeclaration),
]
),
],
};
}
export function createMemberName(name: string, mapNullishTo: string = 'Unknown'): any {
let label = pascalCase(name) ?? 'Unknown';
if (!label.substring(0, 1).match(/[a-zA-Z]/)) {
if(!isNaN(Number.parseInt(label)))
{
return factory.createIdentifier(`_${label}`);
}
return factory.createStringLiteral(label);
}
return factory.createIdentifier(label);
}