@senx/discovery-code
Version:
Discovery Code Editor
441 lines (435 loc) • 17.8 kB
JavaScript
;
/*
* Copyright 2023 SenX S.A.S.
*
* 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.
*/
class Utils {
static mergeDeep(...sources) {
// Variables
const extended = {};
const deep = true;
let i = 0;
// Merge the object into the extended object
// Loop through each object and conduct a merge
for (; i < sources.length; i++) {
const obj = sources[i];
Utils.merge(obj, extended, deep);
}
return extended;
}
static unsescape(str) {
return str.replace(/>/g, '>')
.replace(/</g, '<')
.replace(/"/g, '"')
.replace(/'/g, '\'')
.replace(/&/g, '&');
}
static merge(obj, extended, deep) {
for (const prop in obj) {
if (obj.hasOwnProperty(prop)) {
// If property is an object, merge properties
if (deep && Object.prototype.toString.call(obj[prop]) === '[object Object]') {
extended[prop] = Utils.mergeDeep(extended[prop], obj[prop]);
}
else {
extended[prop] = obj[prop];
}
}
}
}
static toArray(obj) {
const arr = [];
Object.keys(obj).forEach(k => arr.push(obj[k]));
return arr;
}
static httpPost(theUrl, payload, headers) {
return new Promise((resolve, reject) => {
const xmlHttp = new XMLHttpRequest();
const resHeaders = {};
xmlHttp.onreadystatechange = () => {
xmlHttp.getAllResponseHeaders().split('\n').forEach(header => {
if (header.trim() !== '') {
const h = header.split(':');
resHeaders[h[0].trim()] = h[1].trim().replace('\r', '');
}
});
if (xmlHttp.readyState === 4 && xmlHttp.status === 200) {
resolve({
data: xmlHttp.responseText,
headers: resHeaders,
status: {
ops: parseInt(resHeaders['x-warp10-ops'], 10),
elapsed: parseInt(resHeaders['x-warp10-elapsed'], 10),
fetched: parseInt(resHeaders['x-warp10-fetched'], 10),
},
});
}
else if (xmlHttp.readyState === 4 && xmlHttp.status >= 400) {
reject({
statusText: xmlHttp.statusText,
status: xmlHttp.status,
url: theUrl,
message: `line #${resHeaders['x-warp10-error-line']}: ${resHeaders['x-warp10-error-message']}`,
detail: {
mess: resHeaders['x-warp10-error-message'],
line: resHeaders['x-warp10-error-line'],
},
});
}
else if (xmlHttp.readyState === 4 && xmlHttp.status === 0) {
reject({
statusText: theUrl + ' is unreachable',
status: 404,
url: theUrl,
message: theUrl + ' is unreachable',
detail: {
mess: theUrl + ' is unreachable',
line: -1
},
});
}
};
xmlHttp.open('POST', theUrl, true); // true for asynchronous
xmlHttp.setRequestHeader('Content-Type', 'text/plain; charset=utf-8');
Object.keys(headers || {})
.filter(h => h.toLowerCase() !== 'accept' && h.toLowerCase() !== 'content-type')
.forEach(h => xmlHttp.setRequestHeader(h, headers[h]));
xmlHttp.send(payload);
});
}
static isArray(value) {
return value && typeof value === 'object' && value instanceof Array && typeof value.length === 'number'
&& typeof value.splice === 'function' && !(value.propertyIsEnumerable('length'));
}
}
/*
* Copyright 2020 SenX S.A.S.
*
* 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.
*/
class Config {
constructor() {
this.buttons = {
class: ''
};
this.execButton = {
class: '',
label: 'Execute'
};
this.datavizButton = {
class: '',
label: 'Visualize'
};
this.hover = true;
this.readOnly = false;
this.messageClass = '';
this.errorClass = '';
this.addLocalHeader = false;
this.editor = {
quickSuggestionsDelay: 10,
quickSuggestions: true,
tabSize: 2,
minLineNumber: 10,
enableDebug: false,
rawResultsReadOnly: false,
};
this.codeReview = {
enabled: false,
readonly: false,
cancelButton: {
class: '',
label: 'Cancel'
},
addButton: {
class: '',
label: 'Add comment'
},
replyButton: {
label: 'Reply'
},
removeButton: {
label: 'Remove'
},
editButton: {
label: 'Edit'
}
};
}
}
/*
* Copyright 2020-2022 SenX S.A.S.
*
* 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.
*/
class EditorConfig {
constructor() {
this.quickSuggestionsDelay = 10;
this.quickSuggestions = true;
this.tabSize = 2;
this.minLineNumber = 10;
this.enableDebug = false;
this.rawResultsReadOnly = false;
}
}
/*
* Copyright 2020-2023 SenX S.A.S.
*
* 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.
*/
/**
* Little class to store statement and its offset in the text
*/
class wsStatement {
constructor(statement, offset) {
this.statement = statement;
this.offset = offset;
}
}
class WarpScriptParser {
/**
* Look for a statement position in the recursive tree of macros markers.
*
* @param macroPositions the table of macro positions (output of parseWarpScriptMacros)
* @param offset the absolute position of the start of statement you are looking for
* @param numberOfMacros the expected number of macros
*/
static findMacrosBeforePosition(macroPositions, offset, numberOfMacros) {
for (let idx = 0; idx < macroPositions.length; idx++) {
if (macroPositions[idx] instanceof wsStatement && macroPositions[idx].offset == offset) {
//found the statement. need to return previous macros as ranges
let pidx = idx - 1;
let c = 0;
let startEndList = [];
while (pidx >= 0 && c < numberOfMacros) {
if (macroPositions[pidx] instanceof wsStatement) {
break;
}
else {
if (typeof (macroPositions[pidx]) !== 'number') {
startEndList.push([macroPositions[pidx][0], macroPositions[pidx][macroPositions[pidx].length - 1]]);
c++;
}
}
pidx--;
}
return startEndList;
}
else if (typeof (macroPositions[idx]) === 'number') ;
else {
let r = this.findMacrosBeforePosition(macroPositions[idx], offset, numberOfMacros);
if (null !== r) {
return r;
}
}
}
return null;
}
/**
* Unlike parseWarpScriptMacros, this function return a very simple list of statements (as strings), ignoring comments.
* [ '"HELLO"' '"WORLD"' '+' '2' '2' '*' ]
*
* When called with withPosition true, it returns a list of list than include start and end position of the statement:
* [ [ '"HELLO"' 4 11 ] [ '"WORLD"' 22 29 ] ]
*/
static parseWarpScriptStatements(ws, withPosition = false) {
let i = 0;
let result = [];
while (i < ws.length - 1) { //often test 2 characters
if (ws.charAt(i) == '<' && ws.charAt(i + 1) == '\'') { //start of a multiline, look for end
// console.log(i, 'start of multiline');
let lines = ws.substring(i, ws.length).split('\n');
let lc = 0;
while (lc < lines.length && lines[lc].trim() != '\'>') {
i += lines[lc].length + 1;
lc++;
}
i += lines[lc].length + 1;
// console.log(i, 'end of multiline');
}
if (ws.charAt(i) == '/' && ws.charAt(i + 1) == '*') { //start one multiline comment, seek for end of comment
// console.log(i, 'start of multiline comment');
i++;
while (i < ws.length - 1 && !(ws.charAt(i) == '*' && ws.charAt(i + 1) == '/')) {
i++;
}
i += 2;
// console.log(i, 'end of multiline comment');
}
if (ws.charAt(i) == '/' && ws.charAt(i + 1) == '/') { //start single line comment, seek for end of line
// console.log(i, 'start of a comment');
i++;
while (i < ws.length - 1 && (ws.charAt(i) != '\n')) {
i++;
}
// console.log(i, 'end of a comment');
}
if (ws.charAt(i) == '\'') { //start of string, seek for end
// console.log(i, 'start of string');
let start = i;
i++;
while (i < ws.length && ws.charAt(i) != '\'' && ws.charAt(i) != '\n') {
i++;
}
i++;
result.push(ws.substring(start, i).replace('\r', ''));
// console.log(i, 'end of string');
}
if (ws.charAt(i) == '"') { //start of string, seek for end
// console.log(i, 'start of string');
let start = i;
i++;
while (i < ws.length && ws.charAt(i) != '"' && ws.charAt(i) != '\n') {
i++;
}
// console.log(i, 'end of string');
i++;
result.push(ws.substring(start, i).replace('\r', ''));
}
if (ws.charAt(i) == '<' && ws.charAt(i + 1) == '%') { //start of a macro.
// console.log(i, 'start of macro');
result.push('<%');
i += 2;
}
if (ws.charAt(i) == '%' && ws.charAt(i + 1) == '>') { //end of a macro.
// console.log(i, 'end of macro');
result.push('%>');
i += 2;
}
if (ws.charAt(i) != ' ' && ws.charAt(i) != '\n') {
let start = i;
while (i < ws.length && ws.charAt(i) != ' ' && ws.charAt(i) != '\n') {
i++;
}
if (withPosition) {
result.push([ws.substring(start, i).replace('\r', ''), start, i]);
}
else {
result.push(ws.substring(start, i).replace('\r', ''));
}
}
i++;
}
return result;
}
static extractSpecialComments(executedWarpScript) {
let result = {};
let warpscriptlines = executedWarpScript.split('\n');
result.listOfMacroInclusion = [];
result.listOfMacroInclusionRange = [];
for (let l = 0; l < warpscriptlines.length; l++) {
let currentline = warpscriptlines[l];
if (currentline.startsWith('//')) {
//find and extract // @paramname parameters
let extraparamsPattern = /\/\/\s*@(\w*)\s*(.*)$/g;
let lineonMatch;
let re = RegExp(extraparamsPattern);
while (lineonMatch = re.exec(currentline.replace('\r', ''))) { //think about windows... \r\n in mc2 files !
let parametername = lineonMatch[1];
let parametervalue = lineonMatch[2];
switch (parametername) {
case 'endpoint': // // @endpoint http://mywarp10server/api/v0/exec
result.endpoint = parametervalue; // overrides the Warp10URL configuration
break;
case 'localmacrosubstitution':
result.localmacrosubstitution = ('true' === parametervalue.trim().toLowerCase()); // overrides the substitutionWithLocalMacros
break;
case 'timeunit':
if (['us', 'ms', 'ns'].indexOf(parametervalue.trim()) > -1) {
result.timeunit = parametervalue.trim();
}
break;
case 'preview':
switch (parametervalue.toLowerCase().substring(0, 4)) {
case 'none':
result.displayPreviewOpt = 'X';
break;
case 'gts':
result.displayPreviewOpt = 'G';
break;
case 'imag':
result.displayPreviewOpt = 'I';
break;
case 'json':
result.displayPreviewOpt = 'J';
break;
case 'disc':
result.displayPreviewOpt = 'D';
break;
default:
result.displayPreviewOpt = '';
break;
}
break;
case 'include':
let p = parametervalue.trim();
if (p.startsWith('macro:')) {
p = p.substring(6).trim();
result.listOfMacroInclusion.push(p);
let r = { startLineNumber: l, startColumn: 3, endLineNumber: l, endColumn: currentline.trim().length };
result.listOfMacroInclusionRange.push(r);
}
break;
case 'theme':
result.theme = parametervalue.trim().toLowerCase();
break;
}
}
}
else {
if (currentline.trim().length > 0) {
break; //no more comments at the beginning of the file
}
}
}
return result;
}
static IsWsLitteralString(s) {
// up to MemoryWarpScriptStack, a valid string is:
return (s.length >= 2 && ((s.startsWith('"') && s.endsWith('"')) || (s.startsWith('\'') && s.endsWith('\''))));
}
}
exports.Config = Config;
exports.EditorConfig = EditorConfig;
exports.Utils = Utils;
exports.WarpScriptParser = WarpScriptParser;