UNPKG

symfony-style-console

Version:

Use the style and utilities of the Symfony Console in Node.js

317 lines (316 loc) 11.3 kB
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;