UNPKG

kui-shell

Version:

This is the monorepo for Kui, the hybrid command-line/GUI electron-based Kubernetes tool

652 lines 28.6 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); const debug_1 = require("debug"); const util_1 = require("./util"); const AST = require("./ast"); const subtext_1 = require("./subtext"); const debug = debug_1.default('plugins/wskflow/fsm2graph'); const maxWidth = 100; const defaultHeight = 20; const defaultCharWidth = 5; const defaultCharHeight = 10; const capitalize = (str) => str.charAt(0).toUpperCase() + str.substring(1); function id2log(id) { return id .replace(/-components/g, '') .replace(/__origin/g, '') .replace(/__terminus/g, ''); } class RenderState { constructor(acts) { this.dummyCount = 0; this.taskIndex = 0; this.activations = acts; if (acts) { this.activations.sort((a, b) => { return a.start - b.start; }); } this.visited = undefined; this.graphData = { id: 'root', label: 'root', children: [], edges: [] }; } addDummy(sources = [], targets, obj, options, directionS, directionT) { const dummyId = 'dummy_' + this.dummyCount; let o; let port; this.dummyCount++; obj.children.push(this.drawNodeNew(dummyId, options.label || dummyId, options.type || 'Dummy', sources, options)); if (sources && sources.length > 0) { o = this.drawEdgeNew(sources[0], dummyId, obj); port = o.targetPort; obj.edges.push(o); for (let i = 1; i < sources.length; i++) { obj.edges.push(this.drawEdgeNew(sources[i], dummyId, obj, undefined, directionS, undefined, port)); } } if (targets && targets.length > 0) { o = this.drawEdgeNew(dummyId, targets[0], obj); port = o.sourcePort; obj.edges.push(o); for (let i = 1; i < targets.length; i++) { obj.edges.push(this.drawEdgeNew(dummyId, targets[i], obj, undefined, directionT, port, undefined)); } } return dummyId; } drawNodeNew(id, label, type, properties, options) { const o = { id, label, type, ports: [], value: options && options.value, tooltip: options && options.tooltip, tooltipHeader: options && options.tooltipHeader, properties: {} }; if (this.visited) { if (id === 'Entry') { this.visited[id] = [0]; o.visited = this.visited[id]; } else if (id === 'Exit') { if (Array.isArray(properties)) { properties.forEach(p => { if (this.visited[p]) { this.visited[id] = [this.activations.length - 1]; } }); } o.visited = this.visited[id]; } else { const iid = id2log(id); if (this.visited[iid]) { if (type === 'action') { this.visited[iid].forEach((v, i) => { this.visited[iid][i]++; }); } if (type === 'retain') { if ((this.visited[iid].length >= 1 && id.endsWith('__origin')) || (this.visited[iid].length === 2 && id.endsWith('__terminus'))) { o.visited = this.visited[iid]; } } else { o.visited = this.visited[iid]; } } } } if (this.visited && (this.visited[id] || id === 'Entry')) { o.visited = this.visited[id] || [0]; } if (type !== 'try' && type !== 'handler') { o.properties['de.cau.cs.kieler.portConstraints'] = 'FIXED_ORDER'; } if (options && options.leftToRight) { delete o.properties['de.cau.cs.kieler.portConstraints']; } if (type !== 'Dummy' && type !== 'Exit' && properties) { o.layoutOptions = {}; Object.keys(properties).forEach(p => { o.properties[p] = properties[p]; o.layoutOptions[p] = properties[p]; }); } if (options && options.leftToRight) { o.properties = { direction: 'RIGHT', 'org.eclipse.elk.direction': 'RIGHT' }; } if (o.type === 'action') { if (label.indexOf('|') !== -1) { o.name = label.substring(0, label.indexOf('|')); label = label.substring(label.indexOf('|') + 1); o.label = label; } else { o.name = label; } if (label.lastIndexOf('/') !== -1 && label.lastIndexOf('/') < label.length - 1) { o.label = label.substring(label.lastIndexOf('/') + 1); } o.height = defaultHeight; if (o.label.length < 40) { o.width = o.label.length * defaultCharWidth + 10; } else { o.label = o.label.substring(0, 40) + '...'; o.width = 40 * defaultCharWidth + 10; } if (this.actions) { if (this.actions[o.name] === undefined) this.actions[o.name] = []; this.actions[o.name].push(o.id); } o.taskIndex = this.taskIndex++; } else if (o.type === 'function') { o.fullFunctionCode = label; const prettyCode = label; const { nLines, maxLineLength } = util_1.textualPropertiesOfCode(prettyCode); if (options.renderFunctionsInView) { const charWidthForCode = defaultCharWidth; o.width = Math.min(maxWidth, maxLineLength * charWidthForCode); o.height = Math.max(2.25, nLines) * defaultCharHeight; o.multiLineLabel = prettyCode.split(/[\n\r]/).map(line => { const width = o.width / charWidthForCode; if (width >= line.length) { return line; } else { return line.substring(0, width) + '\u2026'; } }); o.prettyCode = prettyCode; delete o.label; } else { o.width = 8; o.height = 8; o.tooltip = prettyCode; delete o.label; } o.taskIndex = this.taskIndex++; } else if (o.type === 'try_catch') { o.properties = { direction: 'RIGHT', 'org.eclipse.elk.direction': 'RIGHT' }; o.children = [ { id: `${id}-body`, label: 'try', type: 'try', ports: [], properties: {}, children: [], edges: [], visited: this.visited ? this.visited[id2log(`${id}-body`)] : undefined }, { id: `${id}-handler`, label: 'error handler', type: 'handler', ports: [], properties: {}, children: [], edges: [], visited: this.visited ? this.visited[id2log(`${id}-<handler`)] : undefined } ]; o.edges = [this.drawEdgeNew(`${id}-body`, `${id}-handler`, o, undefined, 'RIGHT')]; } else if (o.type === 'Entry' || o.type === 'Exit') { o.width = 18; o.height = 18; } else if (o.type === 'retain') { o.width = 4; o.height = 4; } else if (o.type === 'Dummy') { if (o.label === o.id) { o.width = 4; o.height = 4; } else { o.width = 0.875 * o.label.length * defaultCharWidth; o.height = 0.875 * defaultCharHeight; } if (this.visited && Array.isArray(properties)) { properties.forEach((_s) => { const s = id2log(_s); if (this.visited[s]) { this.visited[s].forEach(a => { if (this.activations[a].response.success) { if (this.visited[o.id] === undefined) { this.visited[o.id] = []; o.visited = []; } this.visited[o.id].push(a); o.visited.push(a); } }); } }); } } else if (o.type === 'let' || o.type === 'literal') { if (o.label.length > 30) { o.width = 30 * defaultCharWidth + 10; } else { o.width = o.label.length * defaultCharWidth + 10; } o.height = defaultHeight; o.tooltip = o.label; delete o.label; o.width = 20; o.height = 20; } else if (o.type === 'retry') { o.children = []; o.edges = []; o.retryCount = label; o.label = `Retry ${label} time${parseInt(label, 10) > 1 ? 's' : ''}`; } else if (o.type === 'repeat') { o.children = []; o.edges = []; o.repeatCount = label; o.label = `Repeat ${label} time${parseInt(label, 10) > 1 ? 's' : ''}`; } return o; } drawEdgeNew(sourceId, targetId, layer, type, direction, sourcePort, targetPort) { for (let i = 0; i < layer.children.length; i++) { if (layer.children[i].id === sourceId) { if (type) { if (type === 'true' || type === 'false') { sourcePort = `${sourceId}_p${type}`; layer.children[i].properties.choice = true; } } else if (layer.children[i].properties.choice) { sourcePort = `${sourceId}_pfalse`; } else { sourcePort = `${sourceId}_p${layer.children[i].ports.length}`; } layer.children[i].ports.push({ id: sourcePort, properties: { portSide: direction || 'SOUTH' } }); } if (layer.children[i].id === targetId) { targetPort = `${targetId}_p${layer.children[i].ports.length}`; layer.children[i].ports.push({ id: targetPort, properties: { portSide: direction || 'NORTH' } }); } if (sourcePort && targetPort) { break; } } if (sourcePort === undefined || targetPort === undefined) { debug('source or target not found', sourceId, targetId, layer, this.graphData); } return { id: sourceId + '_' + sourcePort + '->' + targetId + '_' + targetPort, source: sourceId, sourcePort: sourcePort, target: targetId, targetPort: targetPort, visited: this.visited ? !!(this.visited[sourceId] && this.visited[targetId]) : undefined }; } ir2graph(ir, gm, id, prevId, options = {}) { if (ir.type === 'sequence' || ir.type === 'seq' || Array.isArray(ir)) { let count = 0; let prev; let array; if (AST.isSequence(ir)) { array = ir.components; } else { array = ir; } array.forEach(obj => { if (obj.options && obj.options.helper) { } else { prev = this.ir2graph(obj, gm, `${id}-${count}`, count > 0 ? prev : prevId, options); count++; } }); return prev; } else { if (AST.isAction(ir)) { let name = ir.name; if (ir.displayLabel) { name += `|${ir.displayLabel}`; } gm.children.push(this.drawNodeNew(id, name, ir.type, undefined, options)); if (prevId) { prevId.forEach(pid => gm.edges.push(this.drawEdgeNew(pid, id, gm))); } return [id]; } else if (AST.isFunction(ir)) { gm.children.push(this.drawNodeNew(id, ir.function.exec.prettyCode || ir.function.exec.code, ir.type, undefined, options)); if (prevId) { prevId.forEach(pid => gm.edges.push(this.drawEdgeNew(pid, id, gm))); } return [id]; } else if (AST.isConditional(ir)) { const firstTestId = gm.children.length; const lastTestId = this.ir2graph(ir.test, gm, `${id}-test`, undefined, options); const firstConsId = gm.children.length; const lastConsId = this.ir2graph(ir.consequent, gm, `${id}-consequent`, undefined, options); let firstAltId; let lastAltId; if (ir.alternate.type !== 'empty') { firstAltId = gm.children.length; lastAltId = this.ir2graph(ir.alternate, gm, `${id}-alternate`, undefined, options); } if (prevId) { prevId.forEach(pid => gm.edges.push(this.drawEdgeNew(pid, gm.children[firstTestId].id, gm))); } let ltid; if (lastTestId.length > 1) { ltid = this.addDummy(lastTestId, undefined, gm, options); } else { ltid = lastTestId[0]; } gm.edges.push(this.drawEdgeNew(ltid, gm.children[firstConsId].id, gm, 'true')); if (lastAltId && lastAltId.length > 0) { gm.edges.push(this.drawEdgeNew(ltid, gm.children[firstAltId].id, gm, 'false')); } else { lastAltId = [ltid]; } const exitConcentrator = this.addDummy(lastAltId.concat(lastConsId), undefined, gm, options); return [exitConcentrator]; } else if (AST.isTry(ir)) { gm.children.push(this.drawNodeNew(id, 'Try-Catch', 'try_catch', undefined, options)); if (prevId) { prevId.forEach(pid => gm.edges.push(this.drawEdgeNew(pid, id, gm))); } else { gm.children[gm.children.length - 1].properties.compoundNoParents = true; } const tryCatchPart = gm.children[gm.children.length - 1]; const tryPart = tryCatchPart.children[0]; const catchPart = tryCatchPart.children[1]; this.ir2graph(ir.body, tryPart, tryPart.id + '-components', undefined, options); this.ir2graph(ir.handler, catchPart, catchPart.id + '-components', undefined, options); return [gm.children[gm.children.length - 1].id]; } else if (AST.isWhile(ir) || AST.isDoWhile(ir)) { let firstTestId; let firstBodyId; let lastTestId; let lastBodyId; if (AST.isWhile(ir)) { firstTestId = gm.children.length; lastTestId = this.ir2graph(ir.test, gm, `${id}-test`, undefined, options); if (prevId) { prevId.forEach(pid => gm.edges.push(this.drawEdgeNew(pid, gm.children[firstTestId].id, gm))); } firstBodyId = gm.children.length; lastBodyId = this.ir2graph(ir.body, gm, `${id}-body`, undefined, options); } else if (AST.isDoWhile(ir)) { firstBodyId = gm.children.length; lastBodyId = this.ir2graph(ir.body, gm, `${id}-body`, undefined, options); firstTestId = gm.children.length; lastTestId = this.ir2graph(ir.test, gm, `${id}-test`, undefined, options); } let ltid; let lbid; if (lastTestId.length > 1) { ltid = this.addDummy(lastTestId, undefined, gm, options); } else { ltid = lastTestId[0]; } if (lastBodyId.length > 1) { lbid = this.addDummy(lastBodyId, undefined, gm, options); } else { lbid = lastBodyId[0]; } gm.edges.push(this.drawEdgeNew(ltid, gm.children[firstBodyId].id, gm, 'true')); gm.edges.push(this.drawEdgeNew(lbid, gm.children[firstTestId].id, gm)); if (prevId && (ir.type === 'dowhile' || ir.type === 'dowhile_nosave')) { prevId.forEach(pid => gm.edges.push(this.drawEdgeNew(pid, gm.children[firstBodyId].id, gm))); } return [ltid]; } else if (AST.isRetain(ir)) { gm.children.push(this.drawNodeNew(`${id}__origin`, '', ir.type, undefined, options)); if (prevId) { prevId.forEach(pid => gm.edges.push(this.drawEdgeNew(pid, `${id}__origin`, gm))); } const lastNodes = this.ir2graph(ir.components, gm, id, [`${id}__origin`], options); gm.children.push(this.drawNodeNew(`${id}__terminus`, '', ir.type, undefined, options)); if (lastNodes) { lastNodes.forEach(pid => gm.edges.push(this.drawEdgeNew(pid, `${id}__terminus`, gm))); } const forwardingEdge = this.drawEdgeNew(`${id}__origin`, `${id}__terminus`, gm, undefined, 'EAST'); forwardingEdge.properties = { type: 'retain' }; gm.edges.push(forwardingEdge); return [`${id}__terminus`]; } else if (AST.isRetryOrRepeat(ir)) { gm.children.push(this.drawNodeNew(id, ir.count, ir.type, undefined, options)); if (prevId) { prevId.forEach(pid => gm.edges.push(this.drawEdgeNew(pid, id, gm))); } this.ir2graph(ir.components, gm.children[gm.children.length - 1], `${id}-components`, undefined, options); return [gm.children[gm.children.length - 1].id]; } else if (AST.isLet(ir)) { const s = JSON.stringify(ir.declarations, undefined, 4); gm.children.push(this.drawNodeNew(id, s, ir.type, undefined, Object.assign(options, { value: ir.declarations }))); if (prevId) { prevId.forEach(pid => gm.edges.push(this.drawEdgeNew(pid, id, gm))); } return this.ir2graph(ir.components, gm, `${id}-components`, [id], options); } else if (AST.isLiteral(ir)) { const s = JSON.stringify(ir.value, undefined, 4); gm.children.push(this.drawNodeNew(id, s, ir.type, undefined, Object.assign(options, { value: ir.value }))); if (prevId) { prevId.forEach(pid => gm.edges.push(this.drawEdgeNew(pid, id, gm))); } return [id]; } else if (AST.isFinally(ir)) { const lastBodyNode = this.ir2graph(ir.body, gm, `${id}-body`, prevId, options); return this.ir2graph(ir.finalizer, gm, `${id}-finalizer`, lastBodyNode, options); } else if (AST.isParallelLike(ir)) { const label = ir.type === 'map' || ir.type === 'forall' ? 'Parallel Map over Array' : 'Execute Tasks in Parallel'; const tooltipHeader = ir.type === 'par' || ir.type === 'parallel' ? 'Parallel' : ir.type === 'map' ? 'Map' : ir.type; const tooltip = ir.type === 'map' || ir.type === 'forall' ? 'Executes a single task in parallel for each element of the input array' : 'Executes a set of tasks in parallel, passing the same input data to each task'; let parent = prevId; if (ir.set) { parent = this.ir2graph(ir.set, gm, `${id}-${ir.type}-set`, parent, options); } const fork = this.addDummy(parent, undefined, gm, { label, tooltipHeader, tooltip }); parent = [fork]; let exits; if (AST.isMapLike(ir)) { const body = ir.components || ir.body; const ellipsis = { type: 'literal', value: '...' }; const spreadItOut = [body, body, ellipsis, body]; exits = spreadItOut.map((component, idx) => { return this.ir2graph(component, gm, `${id}-component-${idx}`, parent, options)[0]; }); } else { exits = ir.components.map((component, idx) => { return this.ir2graph(component, gm, `${id}-component-${idx}`, parent, options)[0]; }); } const exitConcentrator = this.addDummy(exits, undefined, gm, { label: 'Wait for Completion', tooltipHeader: `${tooltipHeader} completion`, tooltip: 'Wait for the parallel tasks to complete, and then return an array of results' }); return [exitConcentrator]; } else if (AST.isComponentBearing(ir)) { const label = capitalize(ir.type); const type = ir.type; const body = this.drawNodeNew(id, label, type, undefined, options); body.children = []; body.edges = []; gm.children.push(body); if (prevId) { prevId.forEach(pid => gm.edges.push(this.drawEdgeNew(pid, id, gm))); } this.ir2graph(ir.components, body, `${id}-components`, undefined, options); return [id]; } else if (typeof ir === 'object') { const type = 'action'; const label = ir.type; gm.children.push(this.drawNodeNew(id, label, type, undefined, options)); if (prevId) { prevId.forEach(pid => gm.edges.push(this.drawEdgeNew(pid, id, gm))); } debug('generic handler', ir, label, type); return [id]; } } } } const numNonFunctions = (composition) => { if (composition === undefined) return 0; if (composition.type === 'function') { return 0; } else if (composition.type) { let sum = 0; for (const key in composition) { sum += numNonFunctions(composition[key]); } return sum + 1; } else if (Array.isArray(composition)) { return composition.reduce((sum, sub) => sum + numNonFunctions(sub), 0); } else { return 0; } }; const isSimpleComposition = (ir) => { const isShort = AST.isComponentArrayBearing(ir) ? ir.components.length <= 2 : true; const numNonFuncs = numNonFunctions(ir); const atMostOneNonFunction = numNonFuncs <= 3; debug('isSimpleComposition', isShort, numNonFuncs); return isShort && atMostOneNonFunction; }; function fsm2graph(tab, ir, containerElement, acts, options, rule) { return __awaiter(this, void 0, void 0, function* () { const renderState = new RenderState(acts); if (renderState.activations) { debug('activations', renderState.activations); renderState.visited = {}; renderState.activations.forEach((a, index) => { if (a.logs) { a.logs.forEach(log => { if (log.indexOf('Entering composition') !== -1) { let path = log.substring(log.lastIndexOf(' ') + 1); path = path.replace(/[\[\.]/g, '-').replace(/\]/g, ''); if (renderState.visited[path] === undefined) renderState.visited[path] = []; renderState.visited[path].push(index); } }); } }); Object.keys(renderState.visited).forEach(k => { const seg = k.split('-'); seg.pop(); const path = seg.join('-'); if (renderState.visited[path] === undefined) renderState.visited[path] = []; renderState.visited[path] = renderState.visited[path].concat(renderState.visited[k]); }); debug('visited nodes:', renderState.visited); } else { renderState.actions = {}; } debug('generating graph model'); const renderFunctionsInView = isSimpleComposition(ir); const viewOptions = Object.assign({ renderFunctionsInView }, options); if ((rule && rule.trigger) || AST.isOn(ir)) { debug('using rule as start', rule); const start = renderState.drawNodeNew('Entry', 'trigger', 'Entry'); start.properties.kind = 'trigger'; start.properties.kindDetail = rule ? rule.trigger.name : AST.isOn(ir) && ir.trigger; start.width = 24; renderState.graphData.children.push(start); if (AST.isOn(ir)) { ir = ir.components; } } else { renderState.graphData.children.push(renderState.drawNodeNew('Entry', 'start', 'Entry')); } let lastNodes = renderState.ir2graph(ir, renderState.graphData, 'composition', ['Entry'], viewOptions); if (lastNodes === undefined) { lastNodes = ['Entry']; } renderState.graphData.children.push(renderState.drawNodeNew('Exit', 'end', 'Exit', lastNodes)); lastNodes.forEach(pid => renderState.graphData.edges.push(renderState.drawEdgeNew(pid, 'Exit', renderState.graphData))); debug('graphData', renderState.graphData); const subtext = yield subtext_1.default(tab, renderState.actions, renderState.activations, renderState.graphData, options); debug('subtext', subtext); debug('inserting DOM, calling graph2doms'); const graph2doms = (yield Promise.resolve().then(() => require('./graph2doms'))).default; const response = yield graph2doms(tab, renderState.graphData, containerElement, renderState.activations); return Object.assign(response, { subtext }); }); } exports.default = fsm2graph; //# sourceMappingURL=fsm2graph.js.map