blob: 24e291caced7e403c23bf7f24c06f7b4fcfbc71a [file] [log] [blame]
/**
*
* Copyright (c) 2020 Silicon Labs
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @module JS API: generator logic
*/
const queryPackage = require('../db/query-package.js')
const queryEndpointType = require('../db/query-endpoint-type.js')
const queryDeviceType = require('../db/query-device-type.js')
const dbEnum = require('../../src-shared/db-enum.js')
const env = require('../util/env')
const _ = require('lodash')
/**
* All promises used by the templates should be synchronizable.
*
* @param {*} promise
*/
function makeSynchronizablePromise(promise) {
// If promise is already synchronizable, just return it.
if (promise.isResolved) return promise
// Set initial state of flags
let isPending = true
let isRejected = false
let isResolved = false
// Resolve the promise, observing its rejection or resolution.
let synchronizablePromise = promise.then(
function (resolutionValue) {
isResolved = true
isPending = false
return resolutionValue
},
function (rejectionError) {
isRejected = true
isPending = false
throw rejectionError
}
)
// Inject check functions.
synchronizablePromise.isResolved = function () {
return isResolved
}
synchronizablePromise.isPending = function () {
return isPending
}
synchronizablePromise.isRejected = function () {
return isRejected
}
return synchronizablePromise
}
/**
* Helpful function that collects the individual blocks by using elements of an array as a context,
* executing promises for each, and collecting them into the outgoing string.
*
* @param {*} resultArray
* @param {*} options Options passed from a block helper.
* @param {*} context The context from within this was called.
* @returns Promise that resolves with a content string.
*/
async function collectBlocks(resultArray, options, context) {
let promises = []
let index = 0
resultArray.forEach((element) => {
let newContext = {
global: context.global,
parent: context,
index: index++,
count: resultArray.length,
...element
}
let block = options.fn(newContext)
promises.push(block)
})
// The else block gets executed if the list is empty.
if (resultArray.length == 0) {
promises.push(
options.inverse({
global: context.global,
parent: context
})
)
}
return Promise.all(promises).then((blocks) => {
let ret = ''
blocks.forEach((b) => {
ret = ret.concat(b)
})
return ret
})
}
/**
* Returns the promise that resolves with the ZCL properties package id.
*
* @param {*} context
* @returns promise that resolves with the package id.
*/
async function ensureZclPackageId(context) {
if ('zclPackageId' in context.global) {
return context.global.zclPackageId
} else {
let pkgs = await queryPackage.getSessionPackagesByType(
context.global.db,
context.global.sessionId,
dbEnum.packageType.zclProperties
)
if (pkgs.length == 0) {
return null
} else {
context.global.zclPackageId = pkgs[0].id
return pkgs[0].id
}
}
}
/**
* Returns the promise that resolves with all ZCL package id specific to current session.
*
* @param {*} context
* @returns promise that resolves with a list of package id.
*/
async function ensureZclPackageIds(context) {
let packageCategory = null
if (context.global.genTemplatePackage != null) {
packageCategory = context.global.genTemplatePackage.category
}
let resPkgIds = []
if ('zclPackageIds' in context.global) {
let pkgIds = context.global.zclPackageIds
if (!packageCategory) {
return pkgIds
} else {
for (let i = 0; i < pkgIds.length; i++) {
if (context.global.packageCache == null) {
context.global.packageCache = new Map()
}
let zclPkg = context.global.packageCache.get(pkgIds[i])
if (zclPkg == null) {
zclPkg = await queryPackage.getPackageByPackageId(
context.global.db,
pkgIds[i]
)
context.global.packageCache.set(pkgIds[i], zclPkg)
}
// Checking for category match or custom xml
if (
zclPkg.category == packageCategory ||
zclPkg.type == dbEnum.packageType.zclXmlStandalone
) {
resPkgIds.push(pkgIds[i])
}
}
return resPkgIds
}
} else {
let pkgs = await queryPackage.getSessionZclPackageIds(
context.global.db,
context.global.sessionId
)
if (!packageCategory) {
context.global.zclPackageIds = pkgs
return pkgs
} else {
for (let i = 0; i < pkgs.length; i++) {
let zclPkg = await queryPackage.getPackageByPackageId(
context.global.db,
pkgs[i]
)
// Checking for category match or custom xml
if (
zclPkg.category == packageCategory ||
zclPkg.type == dbEnum.packageType.zclXmlStandalone
) {
resPkgIds.push(pkgs[i])
}
}
context.global.zclPackageIds = pkgs
return resPkgIds
}
}
}
/**
* Returns a package category of the toplevel template package from context.
*
* @param {*} context
* @returns proimise that resolves into a package category
*/
async function ensureTemplatePackageCategory(context) {
if (`templatePackageCategory` in context.global) {
return context.global.templatePackageCategory
} else if ('genTemplatePackage' in context.global) {
return context.global.genTemplatePackage.category
} else {
let id = await ensureTemplatePackageId(context)
if (id == null) return null
let pkg = await queryPackage.getPackageByPackageId(context.global.db, id)
context.global.templatePackageCategory = pkg?.category
return pkg?.category
}
}
/**
* Returns the promise that resolves with the ZCL properties package id.
*
* @param {*} context
* @returns promise that resolves with the package id.
*/
async function ensureTemplatePackageId(context) {
if ('templatePackageId' in context.global) {
return context.global.templatePackageId
} else if ('genTemplatePackageId' in context.global) {
return context.global.genTemplatePackageId
} else {
let pkgs = await queryPackage.getSessionPackagesByType(
context.global.db,
context.global.sessionId,
dbEnum.packageType.genTemplatesJson
)
if (pkgs.length == 0) {
return null
} else {
context.global.templatePackageId = pkgs[0].id
return pkgs[0].id
}
}
}
/**
* Populate the endpoint type ids into the global context.
* @param {*} context
* @returns endpoint type ids
*/
async function ensureEndpointTypeIds(context) {
let packageCategory = null
if (context.global.genTemplatePackage != null) {
packageCategory = context.global.genTemplatePackage.category
}
let resEptIds = []
if ('endpointTypeIds' in context.global) {
let eptIds = context.global.endpointTypeIds
if (!packageCategory) {
return eptIds
} else {
for (let i = 0; i < eptIds.length; i++) {
// Get endpoint type device info
let deviceTypes =
await queryDeviceType.selectDeviceTypesByEndpointTypeId(
context.global.db,
eptIds[i].endpointTypeId
)
// Sometimes a device type cannot be found for an endpoint type(undefined)
if (deviceTypes.length == 0) {
return context.global.endpointTypeIds
}
for (let j = 0; j < deviceTypes.length; j++) {
// Get device info
let deviceType = await queryDeviceType.selectDeviceTypeById(
context.global.db,
deviceTypes[j].deviceTypeRef
)
// Get package information to see the category of the device type
let packageInfo = await queryPackage.getPackageByPackageId(
context.global.db,
deviceType.packageRef
)
// Check for package category match based on gen template category and add it to relevant endpoint types
if (
packageInfo.category == packageCategory ||
!packageCategory ||
(!packageInfo.category &&
packageInfo.type === dbEnum.packageType.zclXmlStandalone)
) {
resEptIds.push(eptIds[i])
break
}
}
}
return resEptIds
}
} else {
let eptIds = await queryEndpointType.selectEndpointTypeIds(
context.global.db,
context.global.sessionId
)
if (!packageCategory) {
context.global.endpointTypeIds = eptIds
return eptIds
} else {
for (let i = 0; i < eptIds.length; i++) {
let deviceTypes =
await queryDeviceType.selectDeviceTypesByEndpointTypeId(
context.global.db,
eptIds[i].endpointTypeId
)
// Sometimes a device type cannot be found for an endpoint type(undefined)
if (deviceTypes.length == 0) {
context.global.endpointTypeIds = eptIds
return eptIds
}
for (let j = 0; j < deviceTypes.length; j++) {
let deviceType = await queryDeviceType.selectDeviceTypeById(
context.global.db,
deviceTypes[j].deviceTypeRef
)
let packageInfo = await queryPackage.getPackageByPackageId(
context.global.db,
deviceType.packageRef
)
if (
packageInfo.category == packageCategory ||
(!packageInfo.category &&
packageInfo.type === dbEnum.packageType.zclXmlStandalone)
) {
resEptIds.push(eptIds[i])
break
}
}
}
context.global.endpointTypeIds = eptIds
return resEptIds
}
}
}
/**
* Resolves with cached cluster extensions, but if they don't
* exist, it will populate them.
*
* @param {*} context
* @param {*} templatePackageId
* @returns promise that resolves with cluster extensions.
*/
async function ensureZclClusterSdkExtensions(context, templatePackageId) {
if ('zclClusterSdkExtension' in context.global) {
return context.global.zclClusterSdkExtension
} else {
let extensions = await queryPackage.selectPackageExtension(
context.global.db,
templatePackageId,
dbEnum.packageExtensionEntity.cluster
)
context.global.zclClusterSdkExtension = extensions
return extensions
}
}
/**
* Resolves with cached cluster extensions, but if they don't
* exist, it will populate them.
*
* @param {*} context
* @param {*} templatePackageId
* @returns promise that resolves with cluster extensions.
*/
async function ensureZclDeviceTypeSdkExtensions(context, templatePackageId) {
if ('zclDeviceTypeExtension' in context.global) {
return context.global.zclDeviceTypeExtension
} else {
let extensions = await queryPackage.selectPackageExtension(
context.global.db,
templatePackageId,
dbEnum.packageExtensionEntity.deviceType
)
context.global.zclDeviceTypeExtension = extensions
return extensions
}
}
/**
* Resolves with cached attribute extensions, but if they don't
* exist, it will populate them.
*
* @param {*} context
* @param {*} templatePackageId
* @returns promise that resolves with attribute extensions.
*/
async function ensureZclAttributeSdkExtensions(context, templatePackageId) {
if ('zclAttributeSdkExtension' in context.global) {
return context.global.zclAttributeSdkExtension
} else {
let extensions = await queryPackage.selectPackageExtension(
context.global.db,
templatePackageId,
dbEnum.packageExtensionEntity.attribute
)
context.global.zclAttributeSdkExtension = extensions
return extensions
}
}
/**
* Resolves with cached attribute type extensions, but if they don't
* exist, it will populate them.
*
* @param {*} context
* @param {*} templatePackageId
* @returns promise that resolves with attribute type extensions.
*/
async function ensureZclAttributeTypeSdkExtensions(context, templatePackageId) {
if ('zclAttributeTypeSdkExtension' in context.global) {
return context.global.zclAttributeTypeSdkExtension
} else {
let extensions = await queryPackage.selectPackageExtension(
context.global.db,
templatePackageId,
dbEnum.packageExtensionEntity.attributeType
)
context.global.zclAttributeTypeSdkExtension = extensions
return extensions
}
}
/**
* Resolves with cached command extensions, but if they don't
* exist, it will populate them.
*
* @param {*} context
* @param {*} templatePackageId
* @returns promise that resolves with command extensions.
*/
async function ensureZclCommandSdkExtensions(context, templatePackageId) {
if ('zclCommandSdkExtension' in context.global) {
return context.global.zclCommandSdkExtension
} else {
let extensions = await queryPackage.selectPackageExtension(
context.global.db,
templatePackageId,
dbEnum.packageExtensionEntity.command
)
context.global.zclCommandSdkExtension = extensions
return extensions
}
}
/**
* Resolves with cached command extensions, but if they don't
* exist, it will populate them.
*
* @param {*} context
* @param {*} templatePackageId
* @returns promise that resolves with command extensions.
*/
async function ensureZclEventSdkExtensions(context, templatePackageId) {
if ('zclEventSdkExtension' in context.global) {
return context.global.zclEventSdkExtension
} else {
let extensions = await queryPackage.selectPackageExtension(
context.global.db,
templatePackageId,
dbEnum.packageExtensionEntity.event
)
context.global.zclEventSdkExtension = extensions
return extensions
}
}
/**
* Every helper that returns a promise, should
* not return the promise directly. So instead of
* returning the promise directly, it should return:
* return templatePromise(this.global, promise)
*
* This will ensure that after tag works as expected.
*
* @param {*} global
* @param {*} promise
*/
function templatePromise(global, promise) {
let syncPromise = makeSynchronizablePromise(promise)
return syncPromise
}
/**
* Function wrapper that can be used when a helper is deprecated.
*
* @param {*} fn
* @param {*} explanation can contain `text`, or `from`/`to`, or just be a string message itself.
* @returns a function that wraps the original function, with deprecation message.
*/
function deprecatedHelper(fn, explanation) {
let msg
let to = null
if (explanation == null) {
msg = `Deprecated helper resolved into ${fn.name}. Please use the new helper directly.`
} else if (_.isString(explanation)) {
msg = explanation
} else if ('text' in explanation) {
msg = explanation.text
} else if ('from' in explanation && 'to' in explanation) {
msg = `Helper ${explanation.from} is deprecated. Use ${explanation.to} instead.`
to = explanation.to
} else if ('to' in explanation) {
msg = `Helper ${fn.name} is deprecated. Use ${explanation.to} instead.`
to = explanation.to
} else if ('from' in explanation) {
msg = `Helper ${explanation.from} is deprecated. Use ${fn.name} instead.`
} else {
msg = `Deprecated helper resolved into ${fn.name}. Please use the new helper directly.`
}
let f = function () {
if (
this.global != undefined &&
this.global.disableDeprecationWarnings != true &&
this.global.deprecationWarnings != undefined &&
this.global.deprecationWarnings[fn.name] == null
) {
this.global.deprecationWarnings[fn.name] = true
env.logWarning(`${this.global.templatePath} : ${msg}`)
}
return fn.apply(this, arguments)
}
f.originalHelper = fn.name
f.isDeprecated = true
if (to != null) f.replacementHelper = to
return f
}
exports.collectBlocks = collectBlocks
exports.ensureZclPackageId = ensureZclPackageId
exports.ensureZclPackageIds = ensureZclPackageIds
exports.ensureTemplatePackageId = ensureTemplatePackageId
exports.ensureTemplatePackageCategory = ensureTemplatePackageCategory
exports.ensureZclClusterSdkExtensions = ensureZclClusterSdkExtensions
exports.ensureZclAttributeSdkExtensions = ensureZclAttributeSdkExtensions
exports.ensureZclAttributeTypeSdkExtensions =
ensureZclAttributeTypeSdkExtensions
exports.ensureZclCommandSdkExtensions = ensureZclCommandSdkExtensions
exports.ensureZclEventSdkExtensions = ensureZclEventSdkExtensions
exports.ensureZclDeviceTypeSdkExtensions = ensureZclDeviceTypeSdkExtensions
exports.ensureEndpointTypeIds = ensureEndpointTypeIds
exports.makeSynchronizablePromise = makeSynchronizablePromise
exports.templatePromise = templatePromise
exports.deprecatedHelper = deprecatedHelper