DATAmaestro Scripts

Related tickets : https://pepite.atlassian.net/browse/DATMA-2916

 

 

/** * –––––––––– * | Jira | * –––––––––– // Please mention on ticket DATMA-2916 when you use this script, and describe your Use Case a bit. /** * –––––––––– * | Config | * –––––––––– */ var baseDir = VirtualPath.parse('Valtris.be/INTENSITES_MOTEURS'); var sourceDir = baseDir; var destinationDir = baseDir.resolve('computed', 'Données nettoyées'); var scriptName = "My script name" var scriptHost = 'https://test.mydatamaestro.com/' //------------------------------------------------ //CHOOSE TAG FILTER RULE (3 methods) /** * @type {FilterTagRule} */ var baseTagFilterRule = { type: 'browse', wildcard: '*.INTENSITE', //Check sur une string, * = N'importe quelle string sourceDir: sourceDir // can add FullPath (true, false) }; // OR define by list /** * @type {FilterTagRule} */ // var _baseTagFilterRule = { // type: 'list', // tags:[tag1Path, tag2Path] // }; //OR BASED on a function // var baseTagFilterRule = { // type: 'browse', // predicate: myFunction // sourceDir: sourceDir // }; //------------------------------------------------ //Do the mapping for each individual computed tag //Modify this function to give all inputs as arguments /** * @type {(tag:JavaVirtualPath, index:number)=>JavaVirtualPath[]} */ var getInputTagsRule = function (intensiteTag) { var parent = intensiteTag.getParent(); var name = intensiteTag.getLastToken(); var nextName = name + '_NOMINALE'; return [intensiteTag, parent.resolve(nextName)]; }; /** * @type {(tag:JavaVirtualPath, index:number)=>TagCreateConfig} */ var outputTagGetter = function (intensiteTag) { var name = intensiteTag.getLastToken(); var nextName = name + '_NETTOYEE'; // var meta= tagConnection.getTagMetadata(intensiteTag) // return createConfigFromTagPath(destinationDir.resolve(nextName),meta); //copy source tag metadata return createConfigFromTagPath(destinationDir.resolve(nextName), {}); //open {} to add parameter like: {isNumeric:false, unit: "ms", title:"", classifier} //Can open {} do ctrl space and scroll down to all the different metaData properties }; /** *@type {ComputeTagFn} */ var customPartagedFunction = function (values, time, lastValue, lastTime) { //Values are in the order defined in getInputTagsRule --> Access with values[0], values[1]... return null; }; /** * @type {boolean} */ var isOptimistic = true; //TAG VALIDY MAP /** * @type {Optional<Optional<number>[]>} */ var validityTagMap = null; //var validityTagMap = [1000,1000] //in MS tag Validy Map is defined identical for all same tag /** * ––––––––––– * | Process | * ––––––––––– */ putTagCreationConfigContext({ isMetadataOwner: true, //False don't update metadata if exist scriptFolder: baseDir, scriptHost: scriptHost, scriptName: scriptName }); function main() { var baseTags = getTags(baseTagFilterRule); var outputTags = []; var outputTagConfigs = []; var inputTags = map(baseTags, function (baseTag, index) { var outputTagConfig = outputTagGetter(baseTag, index); outputTags.push(outputTagConfig.tagPath); outputTagConfigs.push(outputTagConfig); return getInputTagsRule(baseTag, index); }); ensureTagConfigExist(outputTagConfigs); useComputedMultipleTag(inputTags, outputTags, customPartagedFunction, isOptimistic, validityTagMap); } //#region Imports /** * ---------- * | Import | * ---------- */ //#region ../../TagScriptLibrary/CommonLibrary/URL.js // An Helper object to keep information with the URL and the fetch request parameters /** * @template T * @typedef {{ * getValue():T, * getValueAsString():string, * resolved():T, * then<U>(callback:(next:T)=>U):U * }} PepitePromise */ /** * @typedef {{ok:boolean, status:number,statusText:string}} PepiteResponseCommon * @typedef { PepiteResponseCommon & { * ok:false, * json():null, * text():null * }} PepiteResponseFailed * @typedef {{ * ok:true, * status:200, * statusText:"ok" * text():PepitePromise<string>, * json():PepitePromise<unknown> * }} PepiteResponseSuccess * @typedef {PepiteResponseFailed | PepiteResponseSuccess} PepiteResponse */ /** * @typedef {"http"|"https"} Protocol * @typedef {{[key:string]:string}} QueryParams * @typedef { Stringifyable & * { * resolvePath(path:string | string[]):FetchURL, * clone():FetchURL, * addParameter(key:string, value:string):FetchURL, * addParameters(obj:any):FetchURL, * completeFetchRequestParams(partialFetchRequest:Partial<RequestInit>):FetchURL, * changeFetchRequestParams(fetchRequestParams:RequestInit):FetchURL, * useFetch():PepitePromise<PepiteResponse>, * addHeader(key:string, value:any):FetchURL, * addHeaders(map:{[key:string]:any}):FetchURL, * changeBody(body:any):FetchURL * }} URLFN * @typedef {{protocol:Protocol, host:string, port:number, path:PathContainer, queryParams:QueryParams, fetchRequestParams:RequestInit} & URLFN} FetchURL */ /** * @typedef {{tokens:string[], isAbsolute:boolean, clone():PathContainer, resolve(token:string|string[]):PathContainer, toAbsolute():PathContainer, toRelative():PathContainer} & Stringifyable} PathContainer */ /** * @param {string[]} tokens * @param {boolean} isAbolute * @returns {PathContainer} */ function PathContainer(tokens, isAbolute) { function cloneTokens() { var nxt = []; tokens.forEach(function (it) { nxt.push(it); }); return nxt; } function toString() { var str = isAbolute ? '/' : ''; if (tokens.length > 0) { var endWithSlash = tokens[tokens.length - 1].endsWith('/'); str += tokens .map(function (it) { var rts = it; if (rts.endsWith('/')) { rts = rts.substring(0, rts.length - 1); } if (rts.startsWith('/')) { rts = rts.substring(1, rts.length); } return rts; }) .reduce(function (previous, token) { return previous + '/' + token; }); if (endWithSlash) { str += '/'; } } return str; } function clone() { return PathContainer(cloneTokens(), isAbolute); } function toAbsolute() { return PathContainer(cloneTokens(), true); } function toRelative() { return PathContainer(cloneTokens(), false); } /** * @param {string| string[]} token */ function resolve(token) { var nxt = cloneTokens(); if (isTypedArray(token)) { token.forEach(function (it) { nxt.push(it); }); } else { nxt.push(token); } return PathContainer(nxt, isAbolute); } return { tokens: tokens, isAbsolute: isAbolute, clone: clone, resolve: resolve, toAbsolute: toAbsolute, toRelative: toRelative, toString: toString }; } /** * @param {Protocol} protocol * @param {string} host * @param {number} port * @param {PathContainer} path * @param {QueryParams} queryParams * @param {RequestInit} fetchRequestParams * @returns {FetchURL} */ function FetchURL(protocol, host, port, path, queryParams, fetchRequestParams) { function toString() { var p = ''; if (port != 80 && port != 0) { p = ':' + port; } var str = protocol + '://' + host + p + path.toAbsolute().toString(); var withQ = false; for (var k in queryParams) { if (queryParams[k] !== undefined) { if (!withQ) { withQ = true; str += '?'; } else { str += '&'; } var param = queryParams[k]; if (isTypedArray(param)) { for (var i = 0; i < param.length; i++) { if (i != 0) { str += '&'; } str += k + '=' + encodeURIComponent(param[i]); } } else { str += k + '=' + encodeURIComponent(param); } } } return str; } /** * @returns {QueryParams} */ function cloneQueryParams() { /** * @type {QueryParams} */ var queryP = {}; for (var key in queryParams) { queryP[key] = queryParams[key]; } return queryP; } /** * @param {string | string[]} _path * @returns {FetchURL} */ function resolvePath(_path) { var nextPath = path.resolve(_path); return FetchURL(protocol, host, port, nextPath, cloneQueryParams(), fetchRequestParams); } function clone() { return FetchURL(protocol, host, port, path, cloneQueryParams(), fetchRequestParams); } function addParameter(key, value) { var clonedQueryParams = cloneQueryParams(); clonedQueryParams[key] = value; return FetchURL(protocol, host, port, path, clonedQueryParams, fetchRequestParams); } function addParameters(obj) { var clonedQueryParams = cloneQueryParams(); clonedQueryParams = assign(clonedQueryParams, obj); return FetchURL(protocol, host, port, path, clonedQueryParams, fetchRequestParams); } function changeFetchRequestParams(_fetchRequestParams) { return FetchURL(protocol, host, port, path, cloneQueryParams(), _fetchRequestParams); } /** * @returns {PepitePromise<PepiteResponse>} */ function useFetch() { //@ts-ignore The Pepite java interface is not the same as the web fetch API. some overlaps allow the use of the fetch API //but the promise is not the same return fetch(toString(), fetchRequestParams); } /** * @param {Partial<RequestInit>} partialFetchRequest * @returns {FetchURL} */ function completeFetchRequestParams(partialFetchRequest) { var nextRequestParams = assign({}, fetchRequestParams); nextRequestParams = assign(nextRequestParams, partialFetchRequest); return changeFetchRequestParams(nextRequestParams); } /** * @param {string} key * @param {any} value * @returns {FetchURL} */ function addHeader(key, value) { var headers = assign({}, fetchRequestParams.headers || {}); headers[key] = value; return completeFetchRequestParams({ headers: headers }); } /** * @param {{[key:string]:any}} headers * @returns {FetchURL} */ function addHeaders(headers) { var url = clone(); for (var key in headers) { var value = headers[key]; url = url.addHeader(key, value); } return url; } /** * @param {any} body * @returns {FetchURL} */ function changeBody(body) { return completeFetchRequestParams({ body: body }); } return { protocol: protocol, host: host, path: path, port: port, queryParams: queryParams, fetchRequestParams: fetchRequestParams, toString: toString, resolvePath: resolvePath, clone: clone, addParameter: addParameter, addParameters: addParameters, changeFetchRequestParams: changeFetchRequestParams, useFetch: useFetch, addHeader: addHeader, addHeaders: addHeaders, changeBody: changeBody, completeFetchRequestParams: completeFetchRequestParams }; } /** * @param {string} url * @returns {FetchURL} */ function parseUrl(url) { var protocol = url.split('://')[0]; if (protocol.startsWith('http')) { protocol = protocol; } else { protocol = 'http'; } var l = url.replace(protocol + '://', ''); var hostSplit = l.split(':'); var host = ''; var port = 0; if (hostSplit.length <= 1) { hostSplit = l.split('.'); if (hostSplit.length <= 3) { hostSplit = l.split('/'); host = hostSplit[0]; } else { for (var i = 0; i < 3; i++) { host += hostSplit[i]; } } l = l.replace(host, ''); } else { host = hostSplit[0]; var portSplit = hostSplit[1].split('/'); port = parseInt(portSplit[0]); l = l.replace(host + ':' + port, ''); } var lSplit = l.split('?'); var path = parsePath(lSplit[0]); var queryParamsNotParsed = lSplit[1]; var queryParams = {}; if (queryParamsNotParsed != null) { var queryPairs = queryParamsNotParsed.split('&'); for (var i = 0; i < queryPairs.length; i++) { var keyValue = queryPairs[i].split('='); queryParams[keyValue[0]] = decodeURIComponent(keyValue[1]); } } /** * @type {RequestInit} */ var fetchRequestParams = { method: 'GET' }; //@ts-ignore return FetchURL(protocol, host, port, path, queryParams, fetchRequestParams); } /** * @param {string} path * @returns {PathContainer} */ function parsePath(path) { var isAbsolute = path.startsWith('/'); var index = isAbsolute ? 1 : 0; var tokens = []; var currentToken = ''; var isEscaping = false; while (index < path.length) { var c = path.charAt(index); if (isEscaping) { currentToken += c; isEscaping = false; } else { if (c == '/') { tokens.push(currentToken); currentToken = ''; isEscaping = false; } else if (c == '\\') { isEscaping = true; } else { currentToken += c; } } index++; } if (currentToken != '') { tokens.push(currentToken); } return PathContainer(tokens, isAbsolute); } //#endregion //#region ../../TagScriptLibrary/CommonLibrary/WildCardPredicate.js /** * @typedef {(name:string | {toString():string})=>boolean} WildcardPredicate */ /** * Create a filter function based on a wildcard string * ex: WildcardPredicate('test*')('test123') // true * ex: WildcardPredicate('*test')('123test') // true * ex: WildcardPredicate('test*')('123test') // false * ex: WildcardPredicate('test*123')('test_random_123') // true * ex: WildcardPredicate('test*123')('test123') // true * @param {string} starName * @returns {WildcardPredicate} */ function WildcardPredicate(starName) { var flterValues = starName.split(/\*/gm); var hasStars = starName.indexOf('*') != -1; var startWihStars = starName.startsWith('*'); var endWihStars = starName.endsWith('*'); /** * * @param {string | {toString():string}} _name * @returns {boolean} */ function filter(_name) { var name = typeof _name == 'string' ? _name : _name.toString(); if (!hasStars) { return name == starName; } for (var i = 0; i < flterValues.length; i++) { var fltr = flterValues[i]; if (fltr != '' && fltr.length > 0) { if (i == 0 && !startWihStars) { if (name.startsWith(fltr)) { name = name.substr(fltr.length); } else { return false; } } else if (i == flterValues.length - 1 && !endWihStars) { if (!name.endsWith(fltr)) { return false; } } else { var startOf = name.indexOf(fltr); if (startOf == -1) { return false; } name = name.substr(startOf + fltr.length); } } } return true; } return filter; } //#endregion //#region ../../TagScriptLibrary/CommonLibrary/JavaArrayHelper.js //Common function used with array to be agnostic of the type (either a Java List or a JSArray) /** * The Java List implementation does not possess the method filter, the wrapper will serve this purpose * /!\ will recreate a JS array (not a stream) * @template T * @param {T[] | List<T>} src * @param {(input:T, i:number)=>boolean} filterCallBack * @returns {T[]} */ function filter(src, filterCallBack) { if (isList(src)) { var nxt = []; var max = getLength(src); for (var i = 0; i < max; i++) { if (filterCallBack(src[i], i)) { nxt.push(src[i]); } } return nxt; } else { return src.filter(filterCallBack); } } /** * The Java List implementation does not possess the method map, the wrapper will serve this purpose * /!\ will recreate a JS array (not a stream) * @template T, O * @param {T[] | List<T>} src * @param {(input:T, i:number)=>O} mapper * @returns {O[]} */ function map(src, mapper) { if (isList(src)) { var nxt = []; var max = getLength(src); for (var i = 0; i < max; i++) { nxt.push(mapper(src[i], i)); } return nxt; } else { return src.map(mapper); } } /** * @template T * @param {T[] | List<T>} src */ function getLength(src) { if ('size' in src) { return src.size(); } else { return src.length; } } /** * The Java List implementation does not possess the method forEach, the wrapper will serve this purpose * @template T * @param {T[] | List<T>} src * @param {(input:T, i:number)=>void} onEach */ function forEach(src, onEach) { if (isList(src)) { var max = getLength(src); for (var i = 0; i < max; i++) { onEach(src[i], i); } } else { src.forEach(onEach); } } /** * find the element by predicate * @template T * @template {T} S * @param {T[] | List<T>} src * @param {T extends S ? ((input:T)=>boolean): ((input:T)=>input is S)} predicate * @returns {S | undefined} */ function find(src, predicate) { if (isList(src)) { var max = getLength(src); for (var i = 0; i < max; i++) { if (predicate(src[i])) { return src[i]; } } return undefined; } else { //@ts-ignore return src.find(predicate); } } /** * The Java List implementation does not have all the method like the JSArray object, * Transform the List into a java array * @template T * @param {T[] | List<T>} src * @returns {T[]} */ function asJSArray(src) { //first check if it is not already a JSArray object if (isList(src)) { return map(src, function (it) { return it; }); } else { return src; } } /** * Check if it is a JSArray or a JavaList * @template T * @param {T[] | List<T>} src * @returns {src is List<T>} */ function isList(src) { try { if (typeof src == 'string') { return false; } //@ts-ignore return !(src.length != undefined && src.push != null && src.forEach != null); } finally { return true; } } //#endregion //#region ../../TagScriptLibrary/CommonLibrary/ObjectAssign.js //Clone or assign element to object === to {...obj, ...element} for clone /** * assign the value of obj in target (mutate target) * @template {any} T * @template {any} O * @param {T} target * @param {O} obj * @returns {T & O} */ function assign(target, obj) { for (var key in obj) { var next = obj[key]; if (next !== undefined) { //@ts-ignore target[key] = obj[key]; } } //@ts-ignore return target; } /** * Similar to var out = { ...tag, ...obj} * @template {any} T * @template {Partial<T>} O * @param {O} target * @param {Partial<O> & Omit<T, keyof O>} obj * @returns {T} */ function cloneAssign(target, obj) { var newObj = cloneObj(target); //@ts-ignore return assign(newObj, obj); } /** * Similar to var out = { ...obj } * @template {any} T * @param {T} obj * @returns {T} */ function cloneObj(obj) { return assign({}, obj); } //#endregion //#region ../../TagScriptLibrary/CommonLibrary/isArray.js /** * Check if the object is a JSArray of T or the object T * @template T * @param {T | T[]} obj * @returns {obj is T[]} */ function isTypedArray(obj) { if (Array.isArray(obj)) { return true; } else { if (typeof obj != 'object') { return false; } else { try { return typeof (/**@type {any} */ (obj).length) == 'number'; } catch (e) { return false; } } } } //#endregion //#region ../../TagScriptLibrary/CommonLibrary/ComputeMultipleTag.js //Create a computed tag function to apply on multiple tags /** * @typedef {( values:TagValue[], time:number, lastValue:Optional<TagValue[]>, lastTime:Optional<number>)=>Optional<TagValue>} ComputeTagFn */ /** * @param {JavaVirtualPath[][]} inputsTags The tags to be used as inputs * @param {JavaVirtualPath[]} outputTags The corresponding output tag * @param {ComputeTagFn} computeTagFn The function to be applied on each inputTags (called for each inputTags[i] and outputTags[i] pair) * @param {boolean} [isOptimistic] compute the data until the min date of the input tags included if true, excluded if false, default to true * @param {Optional<Optional<number>[]>} [validityTagMap] The validity of each tags (inputs) */ function useComputedMultipleTag(inputsTags, outputTags, computeTagFn, isOptimistic, validityTagMap) { for (var i = 0; i < inputsTags.length; i++) { useAComputedTag(inputsTags[i], outputTags[i], computeTagFn, isOptimistic == null ? true : isOptimistic, validityTagMap); } } /** * @param {JavaVirtualPath[]} inputsTag * @param {JavaVirtualPath} outputTag * @param {ComputeTagFn} computeTagFn * @param {boolean} isOptimistic * @param {Optional<Optional<number>[]> | undefined} validityTagMap */ function useAComputedTag(inputsTag, outputTag, computeTagFn, isOptimistic, validityTagMap) { var lastDate = tagConnection.getLastDate(outputTag); var lastDateNum = lastDate == null ? null : lastDate.getTime(); /** * @type {SingleRollingInput<TagValue[]>} */ var rollInput = SingleRollingInput(); /** * @type {{lastTime:Optional<number>}} */ var lastTimeHolder = { lastTime: null }; /** * @type {JavaDate | null} */ var minEndTime = null; for (var i = 0; i < inputsTag.length; i++) { var inputTagLastDate = tagConnection.getLastDate(inputsTag[i]); if (inputTagLastDate == null) { //no data for input tag, skip return; } else { var inputTagLastTime = inputTagLastDate.getTime(); var validity = validityTagMap ? validityTagMap[i] : null; if (validity != null) { inputTagLastTime += validity; } if (minEndTime == null || inputTagLastTime < minEndTime.getTime()) { minEndTime = JDate.fromTime(inputTagLastTime); } } } if (isOptimistic) { minEndTime = minEndTime != null ? JDate.fromTime(minEndTime.getTime() + 1) : null; } /** * @param {HistoryRecord} record */ function processor(record) { var recordValues = []; for (var i = 0; i < record.colCount(); i++) { recordValues.push(record.get(i)); } var time = record.getTime(); var lastTime = lastTimeHolder.lastTime; var lastRecordsValues = rollInput.getInput(); var lastValues = lastRecordsValues.previous; var output = computeTagFn(recordValues, time, lastValues, lastTime); if (lastDateNum == null || time > lastDateNum) { tagConnection.putValue(outputTag, JDate.fromTime(time), output); } lastTimeHolder.lastTime = time; rollInput.roll(recordValues); return true; } tagConnection.retrieveValues(lastDate, minEndTime, true, inputsTag).apply(processor); } //#endregion //#region ../../TagScriptLibrary/CommonLibrary/RollInput.js // An input that can be rolled to keep track of previous values /** * @template Values * @typedef {{current:Optional<Values>, previous:Optional<Values>, next:Optional<Values>}} FNInput */ /** * @template Values * @typedef {{current:Optional<Values>, previous:Optional<Values>}} SingleFnInput */ /** * @template Values * @typedef {{size:number, current:Optional<Values>, [back:number]:Optional<Values>}} CountFnInput */ /** * @template V * @typedef {{roll(value:Optional<V>), getInput():FNInput<V> }} RollingInput */ /** * @template V * @typedef {{roll(value:Optional<V>), getInput():SingleFnInput<V> }} SingleRollingInput */ /** * @template V * @typedef {{roll(value:Optional<V>), getInput():CountFnInput<V> }} CountRollingInput */ /** * @template V * @returns {RollingInput<V>} */ function RollingInput() { /** * @type {FNInput<V>} */ var roller = { previous: null, current: null, next: null }; /** * @param {V} value */ function roll(value) { roller.previous = roller.current; roller.current = roller.next; roller.next = value; } /** * @returns {FNInput<V>} */ function getInput() { return assign({}, roller); } return { roll: roll, getInput: getInput }; } /** * @template V * @returns {SingleRollingInput<V>} */ function SingleRollingInput() { /** * @type {SingleFnInput<V>} */ var roller = { previous: null, current: null }; /** * @param {V} value */ function roll(value) { roller.previous = roller.current; roller.current = value; } /** * @returns {SingleFnInput<V>} */ function getInput() { return assign({}, roller); } return { roll: roll, getInput: getInput }; } /** * @template V * @param {number} size * @returns {CountRollingInput<V>} */ function CountRollingInput(size) { /** * @type {CountFnInput<V>} */ var roller = { size: size, current: null }; /** * @param {V} value */ function roll(value) { if (size > 0) { for (var i = -size; i < -1; i++) { roller[i] = roller[i + 1]; } roller[-1] = roller.current; } roller.current = value; } /** * @returns {CountFnInput<V>} */ function getInput() { return assign({}, roller); } return { roll: roll, getInput: getInput }; } //#endregion //#region ../../TagScriptLibrary/CommonLibrary/TypeHelp.js /** * @template T * @typedef {T | null} Optional<T> */ /** * @typedef {()=>void} Toggler * @typedef {{toString():string}} Stringifyable */ /** * @template T * @typedef {{[key:string]:T}} JSMap */ /** * @template { string} K * @template V * @typedef {{[P in K]?:Optional<V>}} PartialEnumTypeToMap */ /** * @template { string} K * @template V * @typedef {{[P in K]:V}} EnumTypeToMap */ /** * @template {string} U * @template {string} F * @typedef {U extends `${infer A}${F}${infer B}` ? U : F} StringMustContains */ /** * @template T * @typedef {T extends null | undefined ? null | undefined : T} IsNotNullType */ /** * @typedef {"Equals" | "Less" | "Greater" } Order */ //#endregion //#region ../../TagScriptLibrary/CommonLibrary/MetaInfo.js /** * @typedef {{name:string, isNumeric:boolean, title?:Optional<string>, classifiers?:Optional<Map<string, any> | JSMap<any>>, description?:Optional<string>, unit?:Optional<string|Unit>}} MetaInfo */ //#endregion //#region ../../TagScriptLibrary/CommonLibrary/isUnit.js /** * @param {string | Unit} unit * @returns {unit is Unit} */ function isUnit(unit) { return isJavaObject(unit); } //#endregion //#region ../../TagScriptLibrary/CommonLibrary/Context.js // Context of the script, to store data to share between function without using global variable (safer as global variable could not be initialized). /** * @type {{[contextName:string]:any}} */ var SCRIPT_CONTEXT; /** * return the context value or create if don't exist. * /!\ As the CONTEXT itself is not created until the code is read (ie, if not in a function that run after the code is completely read) * The @param contextName can be null if store in a variable. * @template T * @param {string} contextName * @param {T} [initialValue] * @returns {(initialValue extends undefined ? Partial<T> : T)} */ function getContext(contextName, initialValue) { if (!isInContext(contextName)) { SCRIPT_CONTEXT[contextName] = initialValue; } return SCRIPT_CONTEXT[contextName]; } /** * return the context value or create if don't exist. * /!\ As the CONTEXT itself is not created until the code is read (ie, if not in a function that run after the code is completely read) * The @param contextName can be null if store in a variable. * @template T * @param {string} contextName * @param {()=>T} initialValue * @returns {T} */ function getContextOrCreate(contextName, initialValue) { if (!isInContext(contextName)) { SCRIPT_CONTEXT[contextName] = initialValue(); } return SCRIPT_CONTEXT[contextName]; } function initialiseContext() { if (SCRIPT_CONTEXT == null) { SCRIPT_CONTEXT = {}; } } /** * @param {string} contextName */ function isInContext(contextName) { initialiseContext(); return contextName in SCRIPT_CONTEXT; } /** * @template T * @param {string} contextName * @param {T} value * @returns {T} */ function putInContext(contextName, value) { initialiseContext(); SCRIPT_CONTEXT[contextName] = value; return SCRIPT_CONTEXT[contextName]; } /** * @template {{[key:string]:any}} T * @template {keyof T} U * @param {string} contextName * @param {U} key * @param {T[U]} value * @returns {T} */ function putContextValue(contextName, key, value) { var context = /** @type {T} */ (getContext(contextName, {})); context[key] = value; return context; } /** * @template {{[key:string]:any}} T * @param {string} contextName * @param {Partial<T>} value * @returns {T} */ function updateContext(contextName, value) { var context = /** @type {T} */ (getContext(contextName, {})); return assign(context, value); } //#endregion //#region ../../TagScriptLibrary/CommonLibrary/tagCreationConfig.js // Help with the creation of tags and their metadata