UNPKG

@kui-shell/core

Version:

An Electron-based shell for cloud-native development

401 lines (397 loc) • 13.4 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.setGraphicalShellIsOpen = exports.print = void 0; var _debug = _interopRequireDefault(require("debug")); var _safe = _interopRequireDefault(require("colors/safe")); var _elementMimic = _interopRequireDefault(require("../util/element-mimic")); var _table = require("../webapp/models/table"); var _entity = require("../models/entity"); var _is = require("../models/mmr/is"); var _NavResponse = require("../models/NavResponse"); var _types = require("../util/types"); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } /* * Copyright 2017 The Kubernetes Authors * * 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. */ const debug = (0, _debug.default)('main/headless-pretty-print'); debug('loading'); const log = console.log; const error = console.error; const colorAlways = process.argv.find(_ => _ === '--color=always'); const neverColor = process.argv.find(_ => _ === '--no-color' || _ === '--no-colors'); const rawOutput = process.argv.find(_ => _ === '--raw-output'); // don't try to pretty-print the JSON; c.f. jq's --raw-output /** * Determine whether how pretty we should make the output; if stdout * is a pipe, then we shouldn't pass through colors, unless * the user specified --color=always. * */ const stdoutIsFIFO = process.platform !== 'win32' && process.stdout.isTTY; const noColor = neverColor || stdoutIsFIFO && !colorAlways; debug('stdoutIsFIFO', stdoutIsFIFO, noColor); const colorMap = { 'var(--color-brand-01)': _safe.default.blue, 'var(--color-brand-02)': _safe.default.blue, 'var(--color-support-02)': _safe.default.blue, 'var(--color-black)': _safe.default.black, 'var(--color-red)': _safe.default.red, 'var(--color-green)': _safe.default.green, 'var(--color-yellow)': _safe.default.yellow, 'var(--color-blue)': _safe.default.blue, 'var(--color-magenta)': _safe.default.magenta, 'var(--color-cyan)': _safe.default.cyan, 'var(--color-white)': _safe.default.white, 'var(--color-gray)': _safe.default.gray, 'var(--color-light-red)': _safe.default.red, 'var(--color-light-green)': _safe.default.green, 'var(--color-light-yellow)': _safe.default.yellow }; let graphicalShellIsOpen = false; let graphicalShellIsPopup = false; /** @return whether the state has changed as a result of this call */ const setGraphicalShellIsOpen = isPopup => { graphicalShellIsPopup = isPopup; if (!graphicalShellIsOpen) { graphicalShellIsOpen = true; return true; } else { return false; } }; exports.setGraphicalShellIsOpen = setGraphicalShellIsOpen; class DefaultPrettyOptions {} let firstPrettyDom = true; // so we can avoid initial newlines for headers const prettyDom = (dom, logger = log, stream = process.stdout, _color, { columnWidths, extraColor } = new DefaultPrettyOptions()) => { debug('prettyDom'); const isHeader = dom.nodeType === 'h1' || dom.nodeType === 'h2' || dom.nodeType === 'h3' || dom.nodeType === 'h4'; const capitalize = false; const hasMargin = dom.className.indexOf('bx--breadcrumb-item--slash') >= 0 || dom.className.indexOf('left-pad') >= 0 || dom.style['margin'] || dom.style['padding']; if (hasMargin) { stream.write(' '); } const extraColorChoice = isHeader || dom.hasStyle('fontWeight', 'bold') ? _safe.default.bold : dom.hasStyle('fontWeight', 500) ? _safe.default.green : dom.hasStyle('fontSize', '0.875em') ? _safe.default.gray : extraColor || _safe.default.reset; const colorFn = dom.hasStyle('color') && colorMap[dom.style.color] || _color; // debug('colors', isHeader, colorCode, color, extraColorChoice) if (isHeader) { // an extra newline before headers if (firstPrettyDom) { // don't emit a header margin for the very first thing // we print firstPrettyDom = false; } else { logger(); } } if (dom.innerText) { const text = capitalize ? dom.innerText.charAt(0).toUpperCase() + dom.innerText.slice(1) : dom.innerText; stream.write(extraColorChoice(colorFn(text))); } const newline = () => { if (dom.nodeType === 'div' || isHeader) { // not perfect, but treat divs as line breakers logger(); } }; if (hasMargin) { stream.write(' '); } if (dom.innerText) { newline(); } // recurse to the children of this fake DOM dom.children.forEach(child => prettyDom(child, logger, stream, _color, { extraColor: extraColorChoice })); // handle table rows and cells: if (dom.rows) { // scan the table for max column widths const columnWidths = []; dom.rows.forEach(row => { if (row.cells) { row.cells.forEach((cell, idx) => { const length = cell.recursiveInnerTextLength(); if (!columnWidths[idx]) columnWidths[idx] = length;else columnWidths[idx] = Math.max(columnWidths[idx], length); }); } }); dom.rows.forEach(row => { prettyDom(row, logger, stream, _color, { columnWidths }); logger(); // insert a newline after every row }); } if (dom.cells) { dom.cells.forEach((cell, idx) => { prettyDom(cell, logger, stream, _color); if (columnWidths && idx < dom.cells.length - 1) { // pad out this column to the column width (except we don't // need padding in the last column) const slop = columnWidths[idx] - cell.recursiveInnerTextLength(); for (let jj = 0; jj < slop; jj++) { stream.write(' '); } // and then a few more to separate the columns stream.write(' '); } }); } // trailing carriage return? if (isHeader && !dom.innerText) { logger(); } }; /** * Pretty print an object as JSON. This is mostly a stub for now, in * case we want to do something fancier. * */ // eslint-disable-next-line @typescript-eslint/no-explicit-any const prettyJSON = (msg, logger = log) => { const serialized = JSON.stringify(msg, undefined, 2); logger(serialized); }; /** * Render a name with an optional package name * */ const pn = (actionName, packageName) => _safe.default.dim(`${packageName ? packageName + '/' : ''}`) + _safe.default.blue(actionName); /** * Render a date; if it is from today, show just the time * */ const prettyDate = millis => { const date = new Date(millis); const now = new Date(); if (date.getUTCFullYear() === now.getUTCFullYear() && date.getUTCMonth() === now.getUTCMonth() && date.getUTCDate() === now.getUTCDate()) { return date.toLocaleTimeString(); } else { return date.toLocaleString(); } }; /** * Turn an entity into a row, because this entity came as part of an * array of entities * */ const pp = _ => _safe.default.dim(_ ? 'public' : 'private'); // pretty publish const pk = _ => _safe.default.green(_.find(({ key }) => key === 'exec').value); // pretty kind const rowifyAction = ({ name, packageName, publish, annotations, version }) => ({ name: pn(name, packageName), 'published?': pp(publish), kind: pk(annotations), version: _safe.default.dim(version) }); const rowifyPackage = ({ name, publish, binding }) => ({ name: pn(name), 'published?': pp(publish), binding }); const rowifySession = ({ sessionId, name, success, status, start }) => ({ sessionId, app: pn(name), start: _safe.default.dim(prettyDate(start)), status: status === 'pending' ? _safe.default.yellow(status) : success ? _safe.default.green(status) : _safe.default.red(status) }); const rowify = { compositions: ({ name, packageName, prettyKind }) => ({ name: pn(name, packageName), type: prettyKind }), session: rowifySession, activations: ({ activationId, name }) => ({ activationId, name: pn(name) }), actions: rowifyAction, triggers: ({ name, publish }) => ({ name: pn(name), 'published?': pp(publish) }), packages: rowifyPackage, plugins: ({ name, attributes }) => { return { name: pn(name), version: _safe.default.dim(attributes.find(({ key }) => key === 'version').value) }; }, _default: ({ key, name, attributes }) => { const row = {}; row[key || 'name'] = pn(name); attributes.forEach(attr => { if (!attr.placeholderValue) { row[attr.key] = attr.value; } }); return row; }, sequence: rowifyAction, composer: rowifyAction, binding: rowifyPackage, live: rowifySession }; /* function activationId(msg: Entity) { return (msg as { activationId: string }).activationId } function name(msg: Entity) { const entity = msg as { entity: { name: string } } return entity.entity.name } */ /** * Pretty print routine that dispatches to the underlying smarter * pretty printers (such as prettyDom and prettyjson) * */ const print = (msg, logger = log, stream = process.stdout, colorFn = _safe.default.reset, ok = 'ok') => { if (ok === 'error') { colorFn = _safe.default.red; } if (msg && (!graphicalShellIsOpen || graphicalShellIsPopup)) { try { if (msg === true) { // true is the graphical shell's way of telling the repl to print 'ok' debug('printing plain true'); logger(_safe.default.green(ok)); } else if (typeof msg === 'string' || typeof msg === 'boolean' || typeof msg === 'number') { stream.write(msg); } else if (_elementMimic.default.isFakeDom(msg)) { // msg is a DOM facade debug('printing fake dom'); if (msg.className.indexOf('usage-error-wrapper') >= 0) { // print usage errors to stdout stream = process.stdout; logger = log; // color = 'reset' } prettyDom(msg, logger, stream, colorFn); logger(); } else if ((0, _types.isPromise)(msg)) { // msg is a promise; resolve it and try again debug('printing promise'); return msg.then(msg => print(msg, logger, stream, colorFn, ok)); } else if ((0, _table.isTable)(msg)) { if (msg.body.length === 0) { return; } debug('printing table'); // strip off header row, as we'll make our own const type = msg.header && (msg.header.prettyType || msg.header.type) || msg.body.length > 0 && (msg.body[0].prettyType || msg.body[0].type); const print = type && rowify[type] || rowify._default; logger(require('columnify')(msg.body.map(print), { headingTransform: _ => _safe.default.dim(_) })); } else if ((0, _entity.isMixedResponse)(msg)) { msg.forEach(_ => { print(_); stream.write('\n'); }); return logger(_safe.default.green(ok)); } else if ((0, _is.isMultiModalResponse)(msg) || (0, _NavResponse.isNavResponse)(msg)) { throw new Error('cannot format this response in headless mode'); } else if (Array.isArray(msg)) { // msg is an array of stuff debug('printing array'); if (msg.length > 0) { try { if (typeof msg[0] === 'string') { // then we have a simple array of strings, not entities ; msg.forEach(_ => { const logline = _.split(/(stdout|stderr)/); if (logline && logline.length === 3 && !rawOutput) { // then this is a log line const colorFn = logline[1] === 'stdout' ? _safe.default.reset : _safe.default.red; logger(_safe.default.dim(new Date(logline[0].trim()).toLocaleString()) + // timestamp // + ' ' + colors.yellow(logline[1]) // stream (stdout, stderr) colorFn(logline[2].replace(/^:/, ''))); // log message } else { // otherwise, we're not sure what this is, // so do not attempt to render it in a special way logger(_); } }); return logger(_safe.default.green(ok)); } } catch (err) { error(err); } } } else if ((0, _types.isHTML)(msg)) { console.error('cannot format HTML message'); } else if ((0, _entity.isMessageBearingEntity)(msg)) { if (_elementMimic.default.isFakeDom(msg.message)) { // msg.message is a DOM facade prettyDom(msg.message, logger, stream, colorFn); logger(); } else { logger(msg.message); } } else if ((0, _entity.isAbortableResponse)(msg)) { logger(msg.response); } else if (typeof msg === 'object') { prettyJSON(msg, logger); } else { logger(colorFn(msg)); } } catch (err) { debug('got an error', err); logger(_safe.default.red(typeof msg === 'object' ? JSON.stringify(msg) : msg.toString())); } } }; exports.print = print;