From 86313b7684c652b6248a645b4e6f08e1f1af2fc0 Mon Sep 17 00:00:00 2001 From: Rodrigo Rodriguez Date: Sat, 13 Jan 2024 14:23:04 -0300 Subject: [PATCH] fix(basic.gblib): DELETE keyword in storage #400. @othonlima --- boot.mjs | 21 ++-- package.json | 1 + .../basic.gblib/services/DialogKeywords.ts | 38 +++++-- packages/basic.gblib/services/GBVMService.ts | 2 +- .../services/KeywordsExpressions.ts | 85 +++++++++------- .../basic.gblib/services/SystemKeywords.ts | 99 ++++++++++++------- src/util.ts | 28 ++++++ 7 files changed, 184 insertions(+), 90 deletions(-) diff --git a/boot.mjs b/boot.mjs index 14e77d9b..87e43f72 100644 --- a/boot.mjs +++ b/boot.mjs @@ -1,27 +1,32 @@ #!/usr/bin/env node +import chalkAnimation from 'chalk-animation'; import Fs from 'fs'; import Path from 'path'; import { exec } from 'child_process'; import pjson from './package.json' assert { type: 'json' }; +import { GBUtil } from './dist/src/util.js'; // Displays version of Node JS being used at runtime and others attributes. console.log(``); console.log(``); console.log(``); -console.log(``); -console.log(``); -console.log(`General Bots Open-core Server 3.1 is initializing...`); -console.log(`Visit: https://github.com/generalbots.`); -console.log(``); -process.stdout.write(`Enviroment: botserver@${pjson.version}, botlib@${pjson.dependencies.botlib}, botbuilder@${pjson.dependencies.botbuilder}, node@${process.version.replace('v', '')}, ${process.platform} ${process.arch} `); -console.log(``); -console.log(``); +chalkAnimation.karaoke(` + █████ █████ ██ █ █████ █████ ████ ██ ████ █████ █████ ███ ® +██ █ ███ █ █ ██ ██ ██ ██ ██ ██ █ ██ ██ █ █ +██ ███ ████ █ ██ █ ████ █████ ██████ ██ ████ █ █ █ ██ +██ ██ █ █ ██ █ █ ██ ██ ██ ██ ██ ██ █ ██ ██ █ █ + █████ █████ █ ███ █████ ██ ██ ██ ██ █████ ████ █████ █ ███ +`,1000); +await GBUtil.sleep(80); var __dirname = process.env.PWD || process.cwd(); try { var run = () => { + import('./dist/src/app.js').then((gb)=> { + console.log(`\n`); + process.stdout.write(`${pjson.version}, botlib@${pjson.dependencies.botlib}, botbuilder@${pjson.dependencies.botbuilder}, node@${process.version.replace('v', '')}, ${process.platform} ${process.arch} `); console.log(`\n`); gb.GBServer.run() }); diff --git a/package.json b/package.json index 09da0732..c82fbdb4 100644 --- a/package.json +++ b/package.json @@ -101,6 +101,7 @@ "botlib": "3.0.11", "c3-chart-maker": "0.2.8", "cd": "0.3.3", + "chalk-animation": "^2.0.3", "chatgpt": "2.4.2", "chrome-remote-interface": "0.31.3", "cli-progress": "3.11.2", diff --git a/packages/basic.gblib/services/DialogKeywords.ts b/packages/basic.gblib/services/DialogKeywords.ts index 3f6cdd89..4f12a6ad 100644 --- a/packages/basic.gblib/services/DialogKeywords.ts +++ b/packages/basic.gblib/services/DialogKeywords.ts @@ -317,23 +317,34 @@ export class DialogKeywords { } // https://weblog.west-wind.com/posts/2008/Mar/18/A-simple-formatDate-function-for-JavaScript - public async format(value, format) { + public async format({pid, value, format}) { - var date = value; + const { min, user } = await DialogKeywords.getProcessInfo(pid); + const contentLocale = min.core.getParam( + min.instance, + 'Default Content Language', + GBConfigService.get('DEFAULT_CONTENT_LANGUAGE') + ); + + if (!(value instanceof Date)) { + value = SystemKeywords.getDateFromLocaleString(pid,value, contentLocale); + } + var date:any = new Date(value); //don't change original date + if (!format) format = "MM/dd/yyyy"; var month = date.getMonth() + 1; var year = date.getFullYear(); - format = format.replace("MM", month.toString().padL(2, "0")); + format = format.replace("MM", GBUtil.padL(month.toString(), 2, "0")); if (format.indexOf("yyyy") > -1) format = format.replace("yyyy", year.toString()); else if (format.indexOf("yy") > -1) format = format.replace("yy", year.toString().substr(2, 2)); - format = format.replace("dd", date.getDate().toString().padL(2, "0")); + format = format.replace("dd", GBUtil.padL(date.getDate().toString(), 2, "0")); var hours = date.getHours(); if (format.indexOf("t") > -1) { @@ -343,16 +354,17 @@ export class DialogKeywords { format = format.replace("t", "am") } if (format.indexOf("HH") > -1) - format = format.replace("HH", hours.toString().padL(2, "0")); + format = format.replace("HH", GBUtil.padL(hours.toString(), 2, "0")); if (format.indexOf("hh") > -1) { if (hours > 12) hours - 12; if (hours == 0) hours = 12; format = format.replace("hh", hours.toString().padL(2, "0")); } if (format.indexOf("mm") > -1) - format = format.replace("mm", date.getMinutes().toString().padL(2, "0")); + format = format.replace("mm", GBUtil.padL(date.getMinutes().toString(), 2, "0")); if (format.indexOf("ss") > -1) - format = format.replace("ss", date.getSeconds().toString().padL(2, "0")); + format = format.replace("ss", GBUtil.padL(date.getSeconds().toString(), 2, "0")); + return format; } @@ -363,10 +375,18 @@ export class DialogKeywords { * * https://stackoverflow.com/a/1214753/18511 */ - public dateAdd(date, mode, units) { + public async dateAdd({pid, date, mode, units}) { + + const { min, user } = await DialogKeywords.getProcessInfo(pid); + const contentLocale = min.core.getParam( + min.instance, + 'Default Content Language', + GBConfigService.get('DEFAULT_CONTENT_LANGUAGE') + ); + let dateCopy = date; if (!(dateCopy instanceof Date)) { - dateCopy = new Date(dateCopy); + dateCopy = SystemKeywords.getDateFromLocaleString(pid,dateCopy, contentLocale); } var ret = new Date(dateCopy); //don't change original date var checkRollover = function () { diff --git a/packages/basic.gblib/services/GBVMService.ts b/packages/basic.gblib/services/GBVMService.ts index ad25654a..c1f3847b 100644 --- a/packages/basic.gblib/services/GBVMService.ts +++ b/packages/basic.gblib/services/GBVMService.ts @@ -592,7 +592,7 @@ export class GBVMService extends GBService { // Setups interprocess communication from .gbdialog run-time to the BotServer API. - const optsRPC = {callTimeout: this.callTimeout}; + const optsRPC = {callTimeout: this.callTimeout, messageParser: data => {return JSON.parse(data)}}; let url; const agent = http.Agent({ keepAlive: true }); diff --git a/packages/basic.gblib/services/KeywordsExpressions.ts b/packages/basic.gblib/services/KeywordsExpressions.ts index 4230b061..461cdef5 100644 --- a/packages/basic.gblib/services/KeywordsExpressions.ts +++ b/packages/basic.gblib/services/KeywordsExpressions.ts @@ -40,28 +40,31 @@ import Path from 'path'; * Image processing services of conversation to be called by BASIC. */ export class KeywordsExpressions { - private static getParams = (text: string, names) => { - const splitParamsButIgnoreCommasInDoublequotes = (str: string) => { - return str.split(',').reduce( - (accum, curr) => { - if (accum.isConcatting) { - accum.soFar[accum.soFar.length - 1] += ',' + curr; - } else { - if (curr === '') { - curr = null; - } - accum.soFar.push(curr); - } - if (curr.split('"').length % 2 == 0) { - accum.isConcatting = !accum.isConcatting; - } - return accum; - }, - { soFar: [], isConcatting: false } - ).soFar; - }; - const items = splitParamsButIgnoreCommasInDoublequotes(text); + public static splitParamsButIgnoreCommasInDoublequotes = (str: string) => { + return str.split(',').reduce( + (accum, curr) => { + if (accum.isConcatting) { + accum.soFar[accum.soFar.length - 1] += ',' + curr; + } else { + if (curr === '') { + curr = null; + } + accum.soFar.push(curr); + } + if (curr.split('"').length % 2 == 0) { + accum.isConcatting = !accum.isConcatting; + } + return accum; + }, + { soFar: [], isConcatting: false } + ).soFar; + }; + + + private static getParams = (text: string, names) => { + + const items = KeywordsExpressions.splitParamsButIgnoreCommasInDoublequotes(text); let i = 0; let json = ''; @@ -440,8 +443,14 @@ export class KeywordsExpressions { keywords[i++] = [ /^\s*(DELETE)(\s*)(.*)/gim, ($0, $1, $2, $3) => { - const params = this.getParams($3, ['file']); - return `await sys.deleteFile ({pid: pid, ${params}})`; + const params = this.getParams($3, ['table', 'criteria']); + + if (params[1]){ + return `await sys.deleteFromStorage ({pid: pid, ${params}})`; + } + else { + return `await sys.deleteFile ({pid: pid, ${params}})`; + } } ]; @@ -796,7 +805,7 @@ export class KeywordsExpressions { /^\s*(.*)\=\s*(dateadd)(\s*)(.*)/gim, ($0, $1, $2, $3, $4) => { const params = this.getParams($4, ['date', 'mode', 'units']); - return `await dk.dateAdd ({pid: pid, ${params}})`; + return `${$1} = await dk.dateAdd ({pid: pid, ${params}})`; } ]; @@ -974,10 +983,10 @@ export class KeywordsExpressions { ]; keywords[i++] = [ - /^\s*(FORMAT)(\s*)(.*)/gim, - ($0, $1, $2, $3) => { - const params = this.getParams($3, ['value', 'format']); - return `await dk.format({pid: pid, ${params}})`; + /^\s*((?:[a-z]+.?)(?:(?:\w+).)(?:\w+)*)\s*=\s*(FORMAT)(\s*)(.*)/gim, + ($0, $1, $2, $3, $4) => { + const params = this.getParams($4, ['value', 'format']); + return `${$1} = await dk.format({pid: pid, ${params}})`; } ]; @@ -1161,27 +1170,34 @@ export class KeywordsExpressions { $3 = $3.replace(/\"/g, ''); let fields = $3.split(','); const table = fields[0].trim(); + fields.shift(); const fieldsAsText = fields.join(','); - let fieldsNamesOnly = []; let index = 0; - fields.forEach(field => { // Extracts only the last part of the variable like 'column' // from 'row.column'. - const fieldRegExp = /(?:.*\.)(.*)/gim; - let name = fieldRegExp.exec(field)[1] + const fieldRegExp = /(?:.*\.)*(.*)/gim; + let name = fieldRegExp.exec(field.trim())[1] fieldsNamesOnly.push(`'${name}'`); }); let fieldsNames = fieldsNamesOnly.join(','); - return `await sys.saveToStorage({pid: pid, table: "${table}", fieldsValues: [${fieldsAsText}], fieldsNames: [${fieldsNames}] })`; + // Checks if it is a collection or series of params. + return ` + + if (${fields[0]}[0]){ + await sys.saveToStorageBatch({pid: pid, table: ${table}, rows:${fields[0]} }) + }else{ + await sys.saveToStorage({pid: pid, table: ${table}, fieldsValues: [${fieldsAsText}], fieldsNames: [${fieldsNames}] }) + } + `; } ]; @@ -1189,7 +1205,8 @@ export class KeywordsExpressions { /^\s*set\s*(.*)/gim, ($0, $1, $2) => { const params = this.getParams($1, ['file', 'address', 'value']); - return `await sys.set ({pid: pid, handle: page, ${params}})`; + const items = KeywordsExpressions.splitParamsButIgnoreCommasInDoublequotes($1); + return `${items[0]} = await sys.set ({pid: pid, handle: page, ${params}})`; } ]; keywords[i++] = [ diff --git a/packages/basic.gblib/services/SystemKeywords.ts b/packages/basic.gblib/services/SystemKeywords.ts index fe33e255..b40b5331 100644 --- a/packages/basic.gblib/services/SystemKeywords.ts +++ b/packages/basic.gblib/services/SystemKeywords.ts @@ -513,7 +513,7 @@ export class SystemKeywords { * @example SET "file.xlsx", "A2", 4500 * * @example SET page, "elementHTMLSelector", "text" - * + */ public async set({ pid, handle, file, address, value, name = null }): Promise { const { min, user } = await DialogKeywords.getProcessInfo(pid); @@ -973,6 +973,38 @@ export class SystemKeywords { return /^([01]?[0-9]|2[0-3]):[0-5][0-9]$/.test(value); } + public static async getFilter(text) { + let filter; + const operators = [/\<\=/, /\<\>/, /\>\=/, /\/, /\bnot in\b/, /\bin\b/, /\=/]; + let done = false; + await CollectionUtil.asyncForEach(operators, async op => { + var re = new RegExp(op, 'gi'); + const parts = text.split(re); + + if (parts.length === 2 && !done) { + filter = { + columnName: parts[0].trim(), + operator: op.toString().replace(/\\b/g, '').replace(/\//g, '').replace(/\\/g, '').replace(/\b/g, ''), + value: parts[1].trim() + }; + + // Swaps values and names in case of IN operators. + + if (filter.operator === 'not in' || filter.operator === 'in') { + const columnName = filter.columnName; + filter.columnName = filter.value; + filter.value = columnName; + } + + done = true; + } + }); + + return filter; + }; + + + /** * Finds a value or multi-value results in a tabular file. * @@ -1093,35 +1125,6 @@ export class SystemKeywords { rows = results.text; } - let getFilter = async text => { - let filter; - const operators = [/\<\=/, /\<\>/, /\>\=/, /\/, /\bnot in\b/, /\bin\b/, /\=/]; - let done = false; - await CollectionUtil.asyncForEach(operators, async op => { - var re = new RegExp(op, 'gi'); - const parts = text.split(re); - - if (parts.length === 2 && !done) { - filter = { - columnName: parts[0].trim(), - operator: op.toString().replace(/\\b/g, '').replace(/\//g, '').replace(/\\/g, '').replace(/\b/g, ''), - value: parts[1].trim() - }; - - // Swaps values and names in case of IN operators. - - if (filter.operator === 'not in' || filter.operator === 'in') { - const columnName = filter.columnName; - filter.columnName = filter.value; - filter.value = columnName; - } - - done = true; - } - }); - - return filter; - }; const contentLocale = min.core.getParam( min.instance, @@ -1139,7 +1142,7 @@ export class SystemKeywords { let filterIndex = 0; await CollectionUtil.asyncForEach(args, async arg => { - const filter = await getFilter(arg); + const filter = await SystemKeywords.getFilter(arg); if (!filter) { throw new Error(`BASIC: FIND filter has an error: ${arg} check this and publish .gbdialog again.`); } @@ -2322,7 +2325,7 @@ export class SystemKeywords { } }); - const equals = + const equals = typeof (value) === 'string' && typeof (valueFound) === 'string' ? value?.toLowerCase() != valueFound?.toLowerCase() : value != valueFound; @@ -2335,16 +2338,12 @@ export class SystemKeywords { obj[columnName] = value; let criteria = {}; criteria[key1Original] = key1Value; - let item; + await retry( async (bail) => { await t.update(obj, { where: criteria }); - }, - { - retries: 5, - } + }, { retries: 5 } ); - return item; } else { @@ -2565,6 +2564,30 @@ export class SystemKeywords { return { contentType, ext, kind, category: kind['category'] }; } + public async deleteFromStorage({ pid, table, criteria }) { + const { min } = await DialogKeywords.getProcessInfo(pid); + GBLog.info(`BASIC: DELETE (storage) '${table}' where ${criteria}.`); + + const definition = this.getTableFromName(table, min); + const filter = await SystemKeywords.getFilter(criteria); + + await retry( + async (bail) => { + const options = { where: {} }; + options.where = {}; + + options.where[filter['columnName']] = filter['value']; + await definition.destroy(options); + + }, + { + retries: 5, + onRetry: (err) => { GBLog.error(`Retrying SaveToStorageBatch due to: ${err.message}.`); } + } + ); + + } + public async deleteFile({ pid, file }) { const { min } = await DialogKeywords.getProcessInfo(pid); GBLog.info(`BASIC: DELETE '${file.name}'.`); diff --git a/src/util.ts b/src/util.ts index de99ddfc..f652052a 100644 --- a/src/util.ts +++ b/src/util.ts @@ -36,6 +36,34 @@ export class GBUtil { + public static repeat = function (chr, count) { + var str = ""; + for (var x = 0; x < count; x++) { str += chr }; + return str; + } + + public static padL = function (value, width, pad) { + if (!width || width < 1) + return value; + + if (!pad) pad = " "; + var length = width - value.length + if (length < 1) return value.substr(0, width); + + return (GBUtil.repeat(pad, length) + value).substr(0, width); + } + public static padR = function (value, width, pad) { + if (!width || width < 1) + return value; + + if (!pad) pad = " "; + var length = width - value.length + if (length < 1) value.substr(0, width); + + return (value + GBUtil.repeat(pad, length)).substr(0, width); + } + + public static sleep(ms) { return new Promise(resolve => { setTimeout(resolve, ms);