symfony-style-console
Version:
Use the style and utilities of the Symfony Console in Node.js
317 lines (316 loc) • 11.3 kB
JavaScript
import { EOL, DIRECTORY_SEPARATOR } from '../env';
import { countOccurences, lengthWithoutDecoration, wordwrap } from '../Helper/Helper';
import { OUTPUT_NORMAL } from '../Output/OutputInterface';
import ConsoleInput from '../Input/ConsoleInput';
import ConsoleOutput from '../Output/ConsoleOutput';
import BufferedOutput from '../Output/BufferedOutput';
import Questionnaire from '../Helper/Questionnaire';
import Table from '../Helper/Table';
import OutputFormatter from '../Formatter/OutputFormatter';
import OutputStyle from './OutputStyle';
let SymfonyStyle = /** @class */ (() => {
class SymfonyStyle extends OutputStyle {
constructor(input = new ConsoleInput(), output = new ConsoleOutput()) {
super(output);
this.input = input;
this.bufferedOutput = new BufferedOutput(output.getVerbosity(), false, output.getFormatter().clone());
const width = SymfonyStyle.LINE_LENGTH ||
process.stdout.columns ||
SymfonyStyle.MAX_LINE_LENGTH;
this.lineLength = Math.min(width - Number(DIRECTORY_SEPARATOR === '\\'), SymfonyStyle.MAX_LINE_LENGTH);
}
/**
* Formats a message as a block of text.
*
* @param messages The message to write in the block
* @param type The block type (in [] on first line)
* @param style The style to apply to the whole block
* @param prefix The prefix for the block
* @param padding Whether to add vertical padding
* @param escape Whether to escape the messages
*/
block(messages, type, style, prefix = ' ', padding = false) {
if (!Array.isArray(messages))
messages = [messages];
this.autoPrependBlock();
this.writeln(this.createBlock(messages, type, style, prefix, padding, true));
this.newLine();
}
/**
* {@inheritdoc}
*/
title(message) {
this.autoPrependBlock();
this.writeln([
`<comment>${OutputFormatter.escapeTrailingBackslash(message)}</>`,
`<comment>${'='.repeat(lengthWithoutDecoration(this.getFormatter(), message))}</>`
]);
this.newLine();
}
/**
* {@inheritdoc}
*/
section(message) {
this.autoPrependBlock();
this.writeln([
`<comment>${OutputFormatter.escapeTrailingBackslash(message)}</>`,
`<comment>${'-'.repeat(lengthWithoutDecoration(this.getFormatter(), message))}</>`
]);
this.newLine();
}
/**
* {@inheritdoc}
*/
listing(elements) {
this.autoPrependText();
elements = elements.map(element => ` * ${element}`);
this.writeln(elements);
this.newLine();
}
/**
* {@inheritdoc}
*/
text(message) {
this.autoPrependText();
const messages = Array.isArray(message) ? message.slice(0) : [message];
for (const message of messages) {
this.writeln(` ${message}`);
}
}
/**
* Formats a command comment.
*
* @param message
*/
comment(message) {
const messages = Array.isArray(message) ? message.slice(0) : [message];
this.autoPrependBlock();
this.writeln(this.createBlock(messages, null, null, '<fg=default;bg=default> // </>'));
this.newLine();
}
/**
* {@inheritdoc}
*/
success(message) {
this.block(message, 'OK', 'fg=black;bg=green', ' ', true);
}
/**
* {@inheritdoc}
*/
error(message) {
this.block(message, 'ERROR', 'fg=white;bg=red', ' ', true);
}
/**
* {@inheritdoc}
*/
warning(message) {
this.block(message, 'WARNING', 'fg=white;bg=red', ' ', true);
}
/**
* {@inheritdoc}
*/
note(message) {
this.block(message, 'NOTE', 'fg=yellow', ' ! ');
}
/**
* {@inheritdoc}
*/
caution(message) {
this.block(message, 'CAUTION', 'fg=white;bg=red', ' ! ', true);
}
/**
* {@inheritdoc}
*/
table(headers, rows) {
const style = Table.getStyleDefinition('symfony-style-guide').clone();
style.setCellHeaderFormat('<info>%s</info>');
const table = new Table(this);
table.setHeaders(headers);
table.setRows(rows);
table.setStyle(style);
table.render();
this.newLine();
}
/**
* {@inheritdoc}
*/
ask(question, defaultAnswer = null, validator = null) {
if (!this.input.isInteractive())
return Promise.resolve(null);
this.initQuestionnaire();
return this.questionnaire.ask(question, defaultAnswer, validator);
}
/**
* {@inheritdoc}
*/
askHidden(question, validator = null) {
if (!this.input.isInteractive())
return Promise.resolve(null);
this.initQuestionnaire();
return this.questionnaire.askHidden(question, validator);
}
/**
* {@inheritdoc}
*/
confirm(question, defaultAnswer = true) {
if (!this.input.isInteractive())
return Promise.resolve(null);
this.initQuestionnaire();
return this.questionnaire.confirm(question, defaultAnswer);
}
/**
* {@inheritdoc}
*/
choice(question, choices, defaultAnswer = null) {
if (!this.input.isInteractive())
return Promise.resolve(null);
this.initQuestionnaire();
return this.questionnaire.choice(question, choices, defaultAnswer);
}
/**
* {@inheritdoc}
*/
progressStart(max = 0) {
this.progressBar = this.createProgressBar(max);
this.progressBar.start();
}
/**
* {@inheritdoc}
*/
progressAdvance(step = 1) {
this.getProgressBar().advance(step);
}
/**
* {@inheritdoc}
*/
progressSet(step) {
this.getProgressBar().setProgress(step);
}
/**
* {@inheritdoc}
*/
progressFinish() {
this.getProgressBar().finish();
this.newLine(2);
this.progressBar = null;
}
/**
* {@inheritdoc}
*/
createProgressBar(max = 0) {
const progressBar = super.createProgressBar(max);
if ('\\' !== DIRECTORY_SEPARATOR) {
progressBar.setEmptyBarCharacter('░'); // light shade character \u2591
progressBar.setProgressCharacter('');
progressBar.setBarCharacter('▓'); // dark shade character \u2593
}
return progressBar;
}
/**
* {@inheritdoc}
*/
writeln(messages, type = OUTPUT_NORMAL) {
super.writeln(messages, type);
this.bufferedOutput.writeln(this.reduceBuffer(messages), type);
}
/**
* {@inheritdoc}
*/
write(messages, newline = false, type = OUTPUT_NORMAL) {
super.write(messages, newline, type);
this.bufferedOutput.write(this.reduceBuffer(messages), newline, type);
}
/**
* {@inheritdoc}
*/
newLine(count = 1) {
super.newLine(count);
this.bufferedOutput.write('\n'.repeat(count));
}
/**
* @return ProgressBar
*/
getProgressBar() {
if (!this.progressBar) {
throw new Error('The ProgressBar is not started.');
}
return this.progressBar;
}
autoPrependBlock() {
const chars = this.bufferedOutput
.fetch()
.replace(new RegExp(EOL, 'g'), '\n')
.slice(-2);
if (!chars.length) {
return this.newLine(); // Empty history, so we should start with a new line
}
// Prepend new line for each non LF chars (This means no blank line was output before)
this.newLine(2 - (countOccurences(chars, '\n') || 0));
}
autoPrependText() {
const fetched = this.bufferedOutput.fetch();
// Prepend new line if last char isn't EOL:
if ('\n' !== fetched.slice(-1)) {
this.newLine();
}
}
reduceBuffer(messages) {
if (!Array.isArray(messages))
messages = [messages];
// We need to know if the two last chars are EOL
// Preserve the last 4 chars inserted (EOL on windows is two chars) in the history buffer
return [this.bufferedOutput.fetch()]
.concat(messages)
.map(value => value.slice(-4));
}
createBlock(messages, type = null, style = null, prefix = ' ', padding = false, escape = false) {
let indentLength = 0;
let lineIndetation;
const prefixLength = lengthWithoutDecoration(this.getFormatter(), prefix);
const lines = [];
if (null !== type) {
type = `[${type}] `;
indentLength = type.length;
lineIndetation = ' '.repeat(indentLength);
}
for (let key = 0; key < messages.length; key++) {
let message = messages[key];
if (escape) {
message = OutputFormatter.escape(message);
}
lines.push(...wordwrap(message, this.lineLength - prefixLength - indentLength, EOL).split(EOL));
if (messages.length > 1 && key < messages.length - 1) {
lines.push('');
}
}
let firstLineIndex = 0;
if (padding && this.isDecorated()) {
firstLineIndex = 1;
lines.unshift('');
lines.push('');
}
for (let i = 0; i < lines.length; i++) {
let line = lines[i];
if (null !== type) {
line = firstLineIndex === i ? type + line : lineIndetation + line;
}
line = prefix + line;
line += ' '.repeat(this.lineLength - lengthWithoutDecoration(this.getFormatter(), line));
if (style) {
line = `<${style}>${line}</>`;
}
lines[i] = line;
}
return lines;
}
initQuestionnaire() {
if (!this.questionnaire) {
this.questionnaire = new Questionnaire(this);
}
}
}
SymfonyStyle.LINE_LENGTH = 0;
SymfonyStyle.MAX_LINE_LENGTH = 120;
return SymfonyStyle;
})();
export default SymfonyStyle;