UNPKG

@senx/discovery-code

Version:

Discovery Code Editor

436 lines (431 loc) 17.8 kB
/* * 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(/&gt;/g, '>') .replace(/&lt;/g, '<') .replace(/&quot;/g, '"') .replace(/&apos;/g, '\'') .replace(/&amp;/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('\'')))); } } export { Config as C, EditorConfig as E, Utils as U, WarpScriptParser as W };