| /** |
| * |
| * 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. |
| */ |
| |
| /** |
| * This module contains the API for templating. For more detailed instructions, read {@tutorial template-tutorial} |
| * |
| * @module Templating API: toplevel utility helpers |
| */ |
| |
| const templateUtil = require('./template-util.js') |
| const queryPackage = require('../db/query-package.js') |
| |
| /** |
| * Produces the top-of-the-file header for a C file. |
| * |
| * @returns The header content |
| */ |
| function zap_header() { |
| return `// This file is generated by ZCL Advanced Platform generator. Please don't edit manually.` |
| } |
| |
| /** |
| * Simple helper that produces an approved size of identation. |
| * |
| * @returns whitespace that is the identation. |
| */ |
| function ident(cnt) { |
| if (Number.isInteger(cnt)) { |
| return ' '.repeat(cnt) |
| } else { |
| return ' ' |
| } |
| } |
| |
| /** |
| * Return new lines based on the given cnt parameter. |
| * |
| * @param {*} cnt |
| * @returns new line |
| */ |
| function new_line(cnt) { |
| if (Number.isInteger(cnt)) { |
| return '\n'.repeat(cnt) |
| } else { |
| return '\n' |
| } |
| } |
| |
| /** |
| * return back slash |
| * |
| * @returns string |
| */ |
| function backslash() { |
| return '\\' |
| } |
| |
| /** |
| * Block helper that iterates over the package options of a given category. |
| * |
| * @param {*} category |
| * @param {*} options |
| */ |
| async function template_options(options) { |
| return templateUtil |
| .ensureTemplatePackageId(this) |
| .then((packageId) => |
| queryPackage.selectAllOptionsValues( |
| this.global.db, |
| packageId, |
| options.hash.category |
| ) |
| ) |
| .then((ens) => templateUtil.collectBlocks(ens, options, this)) |
| } |
| |
| /** |
| * Inside an iterator, this helper allows you to specify the content that will be output only |
| * during the first element. |
| * |
| * @param {*} options |
| * @returns content, if it's the first element inside an operator, empty otherwise. |
| */ |
| function first(options) { |
| if (this.index != null && this.count != null && this.index == 0) { |
| return options.fn(this) |
| } |
| } |
| |
| /** |
| * Inside an iterator, this helper allows you to specify the content that will be output only |
| * if the element is not the first element. |
| * |
| * @param {*} options |
| * @returns content, if it's the first element inside an operator, empty otherwise. |
| */ |
| function not_first(options) { |
| if (this.index != null && this.count != null && this.index != 0) { |
| return options.fn(this) |
| } |
| } |
| |
| /** |
| * Inside an iterator, this helper allows you to specify the content that will be output only |
| * during the last element. |
| * |
| * @param {*} options |
| * @returns content, if it's the last element inside an operator, empty otherwise. |
| */ |
| function last(options) { |
| if ( |
| this.index != null && |
| this.count != null && |
| this.index == this.count - 1 |
| ) { |
| return options.fn(this) |
| } |
| } |
| |
| /** |
| * Inside an iterator. the block is output only if this is NOT the last item. |
| * Useful for wrapping commas in the list of arguments and such. |
| * |
| * @param {*} optionms |
| * @returns content, if it's not the last element inside a block, empty otherwise. |
| */ |
| function not_last(options) { |
| if ( |
| this.index != null && |
| this.count != null && |
| this.index != this.count - 1 |
| ) { |
| return options.fn(this) |
| } |
| } |
| |
| /** |
| * Inside an iterator, this helper allows you to specify the content that will be output only |
| * during the non-first and no-last element. |
| * |
| * @param {*} options |
| * @returns content, if it's the middle element inside an operator, empty otherwise. |
| */ |
| function middle(options) { |
| if ( |
| this.index != null && |
| this.count != null && |
| this.index != 0 && |
| this.index != this.count - 1 |
| ) { |
| return options.fn(this) |
| } |
| } |
| |
| /** |
| * This fetches a promise which returns template options if provided |
| * |
| * @param {*} options |
| * @param {*} key |
| */ |
| async function template_option_with_code(options, key) { |
| return templateUtil |
| .ensureTemplatePackageId(this) |
| .then((packageId) => |
| queryPackage.selectSpecificOptionValue( |
| this.global.db, |
| packageId, |
| options, |
| key |
| ) |
| ) |
| } |
| |
| /** |
| * Forced fail halper. |
| * |
| * @param {*} options |
| */ |
| function fail(options) { |
| let message = options.hash.message |
| if (message == null) { |
| message = 'Template failure.' |
| } |
| throw new Error(message) |
| } |
| |
| /** |
| * This returns a boolean if the 2 strings are same |
| * |
| * @param {*} string_a |
| * @param {*} string_b |
| */ |
| function isEqual(string_a, string_b) { |
| return string_a.trim() === string_b.trim() |
| } |
| |
| /** |
| * This returns a boolean based on the 2 strings being equal or not given that both |
| * @param {*} string_a |
| * @param {*} string_b |
| */ |
| function is_lowercase_equal(string_a, string_b) { |
| let str1 = string_a.toLowerCase().replace(/"/g, '').trim() |
| let str2 = string_b.toLowerCase().replace(/"/g, '').trim() |
| return 0 == str1.localeCompare(str2) |
| } |
| |
| /** |
| * Return true/false result based on condition. |
| * |
| * @param {*} condition |
| * @param {*} trueResult |
| * @param {*} falseResult |
| * @returns boolean |
| */ |
| function toggle(condition, trueResult, falseResult) { |
| return condition ? trueResult : falseResult |
| } |
| |
| /** |
| * Remove leading and trailing spaces from a string |
| * |
| * @param {*} str |
| * @returns A string with no leading and trailing spaces |
| */ |
| function trim_string(str) { |
| if (str == null) return null |
| return str.trim() |
| } |
| |
| /** |
| * Split the string based on spaces and return the last word |
| * @param {*} str |
| */ |
| function asLastWord(str) { |
| let strings = str.trim().split(' ') |
| if (strings.length > 0) { |
| return strings[strings.length - 1] |
| } |
| return str.trim() |
| } |
| |
| /** |
| * Iteration block. |
| */ |
| function iterate(options) { |
| let hash = options.hash |
| let ret = '' |
| for (let i = 0; i < hash.count; i++) { |
| let newContext = { |
| global: this.global, |
| parent: this, |
| index: i, |
| count: hash.count |
| } |
| ret = ret.concat(options.fn(newContext)) |
| } |
| return ret |
| } |
| |
| /** |
| * Add to accumulator results. |
| * |
| * @param {*} accumulator |
| * @param {*} value |
| */ |
| function addToAccumulator(accumulator, value) { |
| if (!('accumulators' in this.global)) { |
| this.global.accumulators = {} |
| } |
| if (!(accumulator in this.global.accumulators)) { |
| this.global.accumulators[accumulator] = { |
| value: [], |
| sum: [], |
| currentSum: 0 |
| } |
| } |
| this.global.accumulators[accumulator].value.push(value) |
| let lastSum = this.global.accumulators[accumulator].currentSum |
| let newSum |
| if (value != null) { |
| newSum = lastSum + value |
| } else { |
| newSum = lastSum |
| } |
| this.global.accumulators[accumulator].sum.push(newSum) |
| this.global.accumulators[accumulator].currentSum = newSum |
| } |
| |
| /** |
| * Get accumulated information from templates. |
| * |
| * @param {*} options |
| * @returns accumulated details |
| */ |
| function iterateAccumulator(options) { |
| let hash = options.hash |
| if (!('accumulators' in this.global)) { |
| return '' |
| } |
| let accumulator = this.global.accumulators[hash.accumulator] |
| let ret = '' |
| if (accumulator != null) { |
| for (let i = 0; i < accumulator.value.length; i++) { |
| let newContext = { |
| global: this.global, |
| parent: this, |
| index: i, |
| count: accumulator.value.length, |
| sum: accumulator.sum[i], |
| value: accumulator.value[i] |
| } |
| ret = ret.concat(options.fn(newContext)) |
| } |
| } |
| return ret |
| } |
| |
| /** |
| * Waits for promise to be resolved synchronously. |
| * |
| * @param {*} pollInterval |
| * @param {*} promise |
| * @param {*} resolve |
| * @param {*} reject |
| */ |
| function waitForSynchronousPromise(pollInterval, promise, resolve, reject) { |
| if (promise.isResolved()) { |
| resolve() |
| } else if (promise.isRejected()) { |
| reject() |
| } else { |
| setTimeout( |
| () => waitForSynchronousPromise(pollInterval, promise, resolve, reject), |
| pollInterval |
| ) |
| } |
| } |
| |
| /** |
| * Resolves all the given globalPromises promises. |
| * |
| * @param {*} globalPromises |
| */ |
| async function promiseToResolveAllPreviousPromises(globalPromises) { |
| if (globalPromises.length > 0) { |
| let promises = [] |
| globalPromises.forEach((promise) => { |
| promises.push( |
| new Promise((resolve, reject) => { |
| waitForSynchronousPromise(100, promise, resolve, reject) |
| }) |
| ) |
| }) |
| await Promise.all(promises) |
| } |
| } |
| |
| /** |
| * Resolve the after promise after all other promises in the global context |
| * have been resolved. |
| * |
| * @param {*} options |
| * @returns Content after all other content has been resolved. |
| */ |
| async function after(options) { |
| await promiseToResolveAllPreviousPromises(this.global.promises) |
| let newContext = { |
| global: this.global, |
| parent: this |
| } |
| this.global.deferredBlocks.push(options.fn) |
| return options.fn(newContext) |
| } |
| |
| /** |
| * Given: A list of strings |
| * Returns a concatenated string with spaces between each string |
| */ |
| function concatenate() { |
| return Array.prototype.slice.call(arguments, 0, -1).join(' ') |
| } |
| |
| /** |
| * |
| * @param numA |
| * @param numB |
| * @returns true if both numbers are equal else returns false |
| */ |
| function is_num_equal(numA, numB) { |
| return numA == numB |
| } |
| |
| /** |
| * |
| * @param value |
| * @returns true or false based on whether the value is undefined or not |
| */ |
| function is_defined(value) { |
| return !(value == null || value == undefined || value == '') |
| } |
| |
| /** |
| * |
| * @param mainString |
| * @param replaceString |
| * @param replaceWithString |
| * @returns A string replaced with another string in the mainString |
| */ |
| function replace_string(mainString, replaceString, replaceWithString) { |
| return mainString.replace(replaceString, replaceWithString) |
| } |
| |
| /** |
| * |
| * @param str |
| * @param prefixStr |
| * @returns A resultant string with all string values prefixed with prefixStr |
| */ |
| function add_prefix_to_all_strings(str, prefixStr) { |
| // Remove hex values from the string |
| let hexValueMatch = str.match(/[0x|x][0-9|A-F]+/g) |
| let hexValues = hexValueMatch ? hexValueMatch.map(String) : [] |
| let strWithoutHexValues = str |
| for (let h of hexValues) { |
| let re = new RegExp(h, 'g') |
| strWithoutHexValues = strWithoutHexValues.replace(re, '') |
| } |
| //Getting unique strings |
| let strs = strWithoutHexValues |
| .match(/[A-Za-z]+/g) |
| .map(String) |
| .filter((v, i, a) => a.indexOf(v) === i) |
| let res = str |
| |
| for (let s of strs) { |
| // Creating an exception for hex values and not applying this there |
| if (!(s.startsWith('x') || s.startsWith('0x'))) { |
| let re = new RegExp(s, 'g') |
| res = res.replace(re, prefixStr + s) |
| } |
| } |
| return res |
| } |
| |
| /** |
| * |
| * @returns A number which is result of multiplying all the arguments given |
| */ |
| function multiply() { |
| let nums = Array.prototype.slice.call(arguments, 0, -1) |
| return nums.reduce((prev, next) => prev * next, 1) |
| } |
| |
| /** |
| * Returns a boolean based on whether a given value is a power or 2 or not. |
| * @param {*} val |
| * @returns boolean |
| */ |
| function is_power_of_two(val) { |
| return Number.isInteger(Math.log2(val)) |
| } |
| |
| /** |
| * |
| * @param {*} val |
| * @returns true if a string has an underscore in it |
| */ |
| function is_string_underscored(val) { |
| return val && typeof val === 'string' ? val.includes('_') : false |
| } |
| |
| /** |
| * |
| * @param {*} val |
| * @returns val in uppercase |
| */ |
| function as_uppercase(val) { |
| return val ? val.toUpperCase() : '' |
| } |
| |
| const dep = templateUtil.deprecatedHelper |
| |
| // WARNING! WARNING! WARNING! WARNING! WARNING! WARNING! |
| // |
| // Note: these exports are public API. Templates that might have been created in the past and are |
| // available in the wild might depend on these names. |
| // If you rename the functions, you need to still maintain old exports list. |
| exports.zap_header = zap_header |
| exports.indent = ident |
| exports.ident = dep(ident, { to: 'indent' }) |
| exports.template_options = template_options |
| exports.last = last |
| exports.not_last = not_last |
| exports.first = first |
| exports.not_first = not_first |
| exports.middle = middle |
| exports.template_option_with_code = template_option_with_code |
| exports.is_equal = isEqual |
| exports.isEqual = dep(isEqual, { to: 'is_equal' }) |
| exports.trim_string = trim_string |
| exports.as_last_word = asLastWord |
| exports.asLastWord = dep(asLastWord, { to: 'as_last_word' }) |
| exports.iterate = iterate |
| exports.add_to_accumulator = addToAccumulator |
| exports.addToAccumulator = dep(addToAccumulator, { to: 'add_to_accumulator' }) |
| exports.iterate_accumulator = iterateAccumulator |
| exports.iterateAccumulator = dep(iterateAccumulator, { |
| to: 'iterate_accumulator' |
| }) |
| exports.after = after |
| exports.toggle = toggle |
| exports.concatenate = concatenate |
| exports.is_lowercase_equal = is_lowercase_equal |
| exports.new_line = new_line |
| exports.backslash = backslash |
| exports.is_num_equal = is_num_equal |
| exports.is_defined = is_defined |
| exports.fail = fail |
| exports.replace_string = replace_string |
| exports.add_prefix_to_all_strings = add_prefix_to_all_strings |
| exports.multiply = multiply |
| exports.is_string_underscored = is_string_underscored |
| exports.as_uppercase = as_uppercase |
| exports.is_power_of_two = is_power_of_two |