@kui-shell/core
Version:
An Electron-based shell for cloud-native development
401 lines (397 loc) • 13.4 kB
JavaScript
;
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;