Adapted project file structure to conform with composer standards.

This commit is contained in:
fristi 2019-09-26 10:53:16 +02:00
parent 4720c49a85
commit 9c90879777
No known key found for this signature in database
GPG key ID: D5ACA407EBF4B713
8 changed files with 0 additions and 1508 deletions

View file

@ -1,31 +0,0 @@
{
"name": "danmaku/console-writer",
"type": "library",
"description": "Easily adaptable console input/output writer for Symfony.",
"authors": [
{
"name": "Marvin Schreurs",
"email": "fristi@danmaku.moe",
"homepage": "https://fristi.danmaku.moe",
"role": "Developer"
}
],
"support": {
"email": "fristi@danmaku.moe"
},
"keywords": [
"console",
"symfony"
],
"license": "MIT",
"minimum-stability": "dev",
"require": {
"php": ">=7.2.0",
"symfony/console": "^4.2"
},
"autoload": {
"psr-4": {
"Danmaku\\Console\\": "src/Console"
}
}
}

View file

@ -1,485 +0,0 @@
<?php
/**
* InheritableQuestionHelper class source file.
*
* Contains the source code of the InheritableQuestionHelper class.
*
* PHP version 7.2
*
* @package Danmaku\Console\Helper
* @author Marvin Schreurs <fristi@danmaku.moe>
*/
namespace Danmaku\Console\Helper;
use Symfony\Component\Console\Exception\RuntimeException;
use Symfony\Component\Console\Formatter\OutputFormatter;
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\StreamableInputInterface;
use Symfony\Component\Console\Output\ConsoleOutputInterface;
use Symfony\Component\Console\Output\ConsoleSectionOutput;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\ChoiceQuestion;
use Symfony\Component\Console\Question\Question;
/**
* Class InheritableQuestionHelper
*
* @package Danmaku\Console\Helper
* @author Marvin Schreurs <fristi@danmaku.moe>
*/
class InheritableQuestionHelper extends \Symfony\Component\Console\Helper\QuestionHelper
{
protected $inputStream;
protected static $shell;
protected static $stty;
/**
* Asks a question to the user.
*
* @return mixed The user answer
*
* @throws RuntimeException If there is no data to read in the input stream
* @throws \Exception
*/
public function ask(InputInterface $input, OutputInterface $output, Question $question)
{
if ($output instanceof ConsoleOutputInterface) {
$output = $output->getErrorOutput();
}
if (!$input->isInteractive()) {
$default = $question->getDefault();
if (null === $default) {
return $default;
}
if ($validator = $question->getValidator()) {
return \call_user_func($question->getValidator(), $default);
} elseif ($question instanceof ChoiceQuestion) {
$choices = $question->getChoices();
if (!$question->isMultiselect()) {
return isset($choices[$default]) ? $choices[$default] : $default;
}
$default = explode(',', $default);
foreach ($default as $k => $v) {
$v = trim($v);
$default[$k] = isset($choices[$v]) ? $choices[$v] : $v;
}
}
return $default;
}
if ($input instanceof StreamableInputInterface && $stream = $input->getStream()) {
$this->inputStream = $stream;
}
if (!$question->getValidator()) {
return $this->doAsk($output, $question);
}
$interviewer = function () use ($output, $question) {
return $this->doAsk($output, $question);
};
return $this->validateAttempts($interviewer, $output, $question);
}
/**
* {@inheritdoc}
*/
public function getName()
{
return 'question';
}
/**
* Prevents usage of stty.
*/
public static function disableStty()
{
static::$stty = false;
}
/**
* Asks the question to the user.
*
* @return bool|mixed|string|null
*
* @throws RuntimeException In case the fallback is deactivated and the response cannot be hidden
*/
protected function doAsk(OutputInterface $output, Question $question)
{
$this->writePrompt($output, $question);
$inputStream = $this->inputStream ?: STDIN;
$autocomplete = $question->getAutocompleterCallback();
if (null === $autocomplete || !$this->hasSttyAvailable()) {
$ret = false;
if ($question->isHidden()) {
try {
$ret = trim($this->getHiddenResponse($output, $inputStream));
} catch (RuntimeException $e) {
if (!$question->isHiddenFallback()) {
throw $e;
}
}
}
if (false === $ret) {
$ret = fgets($inputStream, 4096);
if (false === $ret) {
throw new RuntimeException('Aborted.');
}
$ret = trim($ret);
}
} else {
$ret = trim($this->autocomplete($output, $question, $inputStream, $autocomplete));
}
if ($output instanceof ConsoleSectionOutput) {
$output->addContent($ret);
}
$ret = \strlen($ret) > 0 ? $ret : $question->getDefault();
if ($normalizer = $question->getNormalizer()) {
return $normalizer($ret);
}
return $ret;
}
/**
* Outputs the question prompt.
*/
protected function writePrompt(OutputInterface $output, Question $question)
{
$message = $question->getQuestion();
if ($question instanceof ChoiceQuestion) {
$maxWidth = max(array_map([$this, 'strlen'], array_keys($question->getChoices())));
$messages = (array) $question->getQuestion();
foreach ($question->getChoices() as $key => $value) {
$width = $maxWidth - $this->strlen($key);
$messages[] = ' [<info>'.$key.str_repeat(' ', $width).'</info>] '.$value;
}
$output->writeln($messages);
$message = $question->getPrompt();
}
$output->write($message);
}
/**
* Outputs an error message.
*/
protected function writeError(OutputInterface $output, \Exception $error)
{
if (null !== $this->getHelperSet() && $this->getHelperSet()->has('formatter')) {
$message = $this->getHelperSet()->get('formatter')->formatBlock($error->getMessage(), 'error');
} else {
$message = '<error>'.$error->getMessage().'</error>';
}
$output->writeln($message);
}
/**
* Autocompletes a question.
*
* @param resource $inputStream
* @return string
*/
private function autocomplete(OutputInterface $output, Question $question, $inputStream, callable $autocomplete): string
{
$fullChoice = '';
$ret = '';
$i = 0;
$ofs = -1;
$matches = $autocomplete($ret);
$numMatches = \count($matches);
$sttyMode = shell_exec('stty -g');
// Disable icanon (so we can fread each keypress) and echo (we'll do echoing here instead)
shell_exec('stty -icanon -echo');
// Add highlighted text style
$output->getFormatter()->setStyle('hl', new OutputFormatterStyle('black', 'white'));
// Read a keypress
while (!feof($inputStream)) {
$c = fread($inputStream, 1);
// as opposed to fgets(), fread() returns an empty string when the stream content is empty, not false.
if (false === $c || ('' === $ret && '' === $c && null === $question->getDefault())) {
shell_exec(sprintf('stty %s', $sttyMode));
throw new RuntimeException('Aborted.');
} elseif ("\177" === $c) { // Backspace Character
if (0 === $numMatches && 0 !== $i) {
--$i;
$fullChoice = substr($fullChoice, 0, -1);
// Move cursor backwards
$output->write("\033[1D");
}
if (0 === $i) {
$ofs = -1;
$matches = $autocomplete($ret);
$numMatches = \count($matches);
} else {
$numMatches = 0;
}
// Pop the last character off the end of our string
$ret = substr($ret, 0, $i);
} elseif ("\033" === $c) {
// Did we read an escape sequence?
$c .= fread($inputStream, 2);
// A = Up Arrow. B = Down Arrow
if (isset($c[2]) && ('A' === $c[2] || 'B' === $c[2])) {
if ('A' === $c[2] && -1 === $ofs) {
$ofs = 0;
}
if (0 === $numMatches) {
continue;
}
$ofs += ('A' === $c[2]) ? -1 : 1;
$ofs = ($numMatches + $ofs) % $numMatches;
}
} elseif (\ord($c) < 32) {
if ("\t" === $c || "\n" === $c) {
if ($numMatches > 0 && -1 !== $ofs) {
$ret = (string) $matches[$ofs];
// Echo out remaining chars for current match
$remainingCharacters = substr($ret, \strlen(trim($this->mostRecentlyEnteredValue($fullChoice))));
$output->write($remainingCharacters);
$fullChoice .= $remainingCharacters;
$i = \strlen($fullChoice);
$matches = array_filter(
$autocomplete($ret),
function ($match) use ($ret) {
return '' === $ret || 0 === strpos($match, $ret);
}
);
$numMatches = \count($matches);
$ofs = -1;
}
if ("\n" === $c) {
$output->write($c);
break;
}
}
continue;
} else {
if ("\x80" <= $c) {
$c .= fread($inputStream, ["\xC0" => 1, "\xD0" => 1, "\xE0" => 2, "\xF0" => 3][$c & "\xF0"]);
}
$output->write($c);
$ret .= $c;
$fullChoice .= $c;
++$i;
$tempRet = $ret;
if ($question instanceof ChoiceQuestion && $question->isMultiselect()) {
$tempRet = $this->mostRecentlyEnteredValue($fullChoice);
}
$numMatches = 0;
$ofs = 0;
foreach ($autocomplete($ret) as $value) {
// If typed characters match the beginning chunk of value (e.g. [AcmeDe]moBundle)
if (0 === strpos($value, $tempRet)) {
$matches[$numMatches++] = $value;
}
}
}
// Erase characters from cursor to end of line
$output->write("\033[K");
if ($numMatches > 0 && -1 !== $ofs) {
// Save cursor position
$output->write("\0337");
// Write highlighted text, complete the partially entered response
$charactersEntered = \strlen(trim($this->mostRecentlyEnteredValue($fullChoice)));
$output->write('<hl>'.OutputFormatter::escapeTrailingBackslash(substr($matches[$ofs], $charactersEntered)).'</hl>');
// Restore cursor position
$output->write("\0338");
}
}
// Reset stty so it behaves normally again
shell_exec(sprintf('stty %s', $sttyMode));
return $fullChoice;
}
private function mostRecentlyEnteredValue($entered)
{
// Determine the most recent value that the user entered
if (false === strpos($entered, ',')) {
return $entered;
}
$choices = explode(',', $entered);
if (\strlen($lastChoice = trim($choices[\count($choices) - 1])) > 0) {
return $lastChoice;
}
return $entered;
}
/**
* Gets a hidden response from user.
*
* @param OutputInterface $output An Output instance
* @param resource $inputStream The handler resource
*
* @return string
* @throws RuntimeException In case the fallback is deactivated and the response cannot be hidden
*/
private function getHiddenResponse(OutputInterface $output, $inputStream): string
{
if ('\\' === \DIRECTORY_SEPARATOR) {
$exe = __DIR__.'/../Resources/bin/hiddeninput.exe';
// handle code running from a phar
if ('phar:' === substr(__FILE__, 0, 5)) {
$tmpExe = sys_get_temp_dir().'/hiddeninput.exe';
copy($exe, $tmpExe);
$exe = $tmpExe;
}
$value = rtrim(shell_exec($exe));
$output->writeln('');
if (isset($tmpExe)) {
unlink($tmpExe);
}
return $value;
}
if ($this->hasSttyAvailable()) {
$sttyMode = shell_exec('stty -g');
shell_exec('stty -echo');
$value = fgets($inputStream, 4096);
shell_exec(sprintf('stty %s', $sttyMode));
if (false === $value) {
throw new RuntimeException('Aborted.');
}
$value = trim($value);
$output->writeln('');
return $value;
}
if (false !== $shell = $this->getShell()) {
$readCmd = 'csh' === $shell ? 'set mypassword = $<' : 'read -r mypassword';
$command = sprintf("/usr/bin/env %s -c 'stty -echo; %s; stty echo; echo \$mypassword'", $shell, $readCmd);
$value = rtrim(shell_exec($command));
$output->writeln('');
return $value;
}
throw new RuntimeException('Unable to hide the response.');
}
/**
* Validates an attempt.
*
* @param callable $interviewer A callable that will ask for a question and return the result
* @param OutputInterface $output An Output instance
* @param Question $question A Question instance
*
* @return mixed The validated response
*
* @throws \Exception In case the max number of attempts has been reached and no valid response has been given
*/
protected function validateAttempts(callable $interviewer, OutputInterface $output, Question $question)
{
$error = null;
$attempts = $question->getMaxAttempts();
while (null === $attempts || $attempts--) {
if (null !== $error) {
$this->writeError($output, $error);
}
try {
return $question->getValidator()($interviewer());
} catch (RuntimeException $e) {
throw $e;
} catch (\Exception $error) {
}
}
throw $error;
}
/**
* Returns a valid unix shell.
*
* @return string|bool The valid shell name, false in case no valid shell is found
*/
protected function getShell()
{
if (null !== static::$shell) {
return static::$shell;
}
static::$shell = false;
if (file_exists('/usr/bin/env')) {
// handle other OSs with bash/zsh/ksh/csh if available to hide the answer
$test = "/usr/bin/env %s -c 'echo OK' 2> /dev/null";
foreach (['bash', 'zsh', 'ksh', 'csh'] as $sh) {
if ('OK' === rtrim(shell_exec(sprintf($test, $sh)))) {
static::$shell = $sh;
break;
}
}
}
return static::$shell;
}
/**
* Returns whether Stty is available or not.
*/
protected function hasSttyAvailable(): bool
{
if (null !== static::$stty) {
return static::$stty;
}
exec('stty 2>&1', $output, $exitcode);
return static::$stty = 0 === $exitcode;
}
}

View file

@ -1,149 +0,0 @@
<?php
/**
* QuestionHelper class source file.
*
* Contains the source code of the QuestionHelper class.
*
* PHP version 7.2
*
* @package Danmaku\Console\Helper
* @author Marvin Schreurs <fristi@danmaku.moe>
*/
namespace Danmaku\Console\Helper;
use Symfony\Component\Console\Exception\RuntimeException;
use Symfony\Component\Console\Formatter\OutputFormatter;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\StreamableInputInterface;
use Symfony\Component\Console\Output\ConsoleOutputInterface;
use Symfony\Component\Console\Output\ConsoleSectionOutput;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\ChoiceQuestion;
use Symfony\Component\Console\Question\ConfirmationQuestion;
use Symfony\Component\Console\Question\Question;
use Symfony\Component\Console\Style\SymfonyStyle;
/**
* Class QuestionHelper
*
* @package Danmaku\Console\Helper
* @author Marvin Schreurs <fristi@danmaku.moe>
*/
class QuestionHelper extends InheritableQuestionHelper
{
public function ask(InputInterface $input, OutputInterface $output, Question $question)
{
if ($output instanceof ConsoleOutputInterface) {
$output = $output->getErrorOutput();
}
if (!$input->isInteractive()) {
$default = $question->getDefault();
if (null === $default) {
return $default;
}
if ($validator = $question->getValidator()) {
return \call_user_func($question->getValidator(), $default);
} elseif ($question instanceof ChoiceQuestion) {
if (!$question->isMultiselect()) {
return $default;
}
$default = explode(',', $default);
foreach ($default as $k => $v) {
$v = trim($v);
$default[$k] = $v;
}
}
return $default;
}
if ($input instanceof StreamableInputInterface && $stream = $input->getStream()) {
$this->inputStream = $stream;
}
if (!$question->getValidator()) {
return $this->doAsk($output, $question);
}
$interviewer = function () use ($output, $question) {
return $this->doAsk($output, $question);
};
return $this->validateAttempts($interviewer, $output, $question);
}
/**
* {@inheritdoc}
*/
protected function writePrompt(OutputInterface $output, Question $question)
{
$text = OutputFormatter::escapeTrailingBackslash($question->getQuestion());
$default = $question->getDefault();
switch (true) {
case null === $default:
$text = sprintf(' <question>%s</question>:', $text);
break;
case $question instanceof ConfirmationQuestion:
$text = sprintf(' <question>%s</question> <info>(yes/no)</info> [<answer>%s</answer>]:', $text, $default ? 'yes' : 'no');
break;
case $question instanceof ChoiceQuestion && $question->isMultiselect():
$choices = $question->getChoices();
$default = explode(',', $default);
foreach ($default as $key => $value) {
$default[$key] = $choices[trim($value)];
}
$text = sprintf(' <question>%s</question> [<answer>%s</answer>]:', $text, OutputFormatter::escape(implode(', ', $default)));
break;
case $question instanceof ChoiceQuestion:
$choices = $question->getChoices();
$text = sprintf(' <question>%s</question> [<answer>%s</answer>]:', $text, OutputFormatter::escape(isset($choices[$default]) ? $choices[$default] : $default));
break;
default:
$text = sprintf(' <question>%s</question> [<answer>%s</answer>]:', $text, OutputFormatter::escape($default));
}
$output->writeln($text);
if ($question instanceof ChoiceQuestion) {
$width = max(array_map('strlen', array_keys($question->getChoices())));
foreach ($question->getChoices() as $key => $value) {
$output->writeln(sprintf(" [<choice>%-${width}s</choice>] %s", $key, $value));
}
}
$output->write(' > ');
}
/**
* {@inheritdoc}
*/
protected function writeError(OutputInterface $output, \Exception $error)
{
if ($output instanceof SymfonyStyle) {
$output->newLine();
$output->error($error->getMessage());
return;
}
parent::writeError($output, $error);
}
}

View file

@ -1,31 +0,0 @@
<?php
/**
* ChoiceQuestion class source file.
*
* Contains the source code of the ChoiceQuestion class.
*
* PHP version 7.2
*
* @package Danmaku\Console\Question
* @author Marvin Schreurs <fristi@danmaku.moe>
*/
namespace Danmaku\Console\Question;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Question\ChoiceQuestion as SymfonyChoiceQuestion;
/**
* Class ChoiceQuestion
*
* @package Danmaku\Console\Question
* @author Marvin Schreurs <fristi@danmaku.moe>
*/
class ChoiceQuestion extends SymfonyChoiceQuestion
{
protected function isAssoc($array)
{
return true;
}
}

View file

@ -1,30 +0,0 @@
<?php
/**
* ConfirmationQuestion class source file.
*
* Contains the source code of the ConfirmationQuestion class.
*
* PHP version 7.2
*
* @package Danmaku\Console\Question
* @author Marvin Schreurs <fristi@danmaku.moe>
*/
namespace Danmaku\Console\Question;
use Symfony\Component\Console\Question\ConfirmationQuestion as SymfonyConfirmationQuestion;
/**
* Class ConfirmationQuestion
*
* @package Danmaku\Console\Question
* @author Marvin Schreurs <fristi@danmaku.moe>
*/
class ConfirmationQuestion extends SymfonyConfirmationQuestion
{
protected function isAssoc($array)
{
return true;
}
}

View file

@ -1,30 +0,0 @@
<?php
/**
* Question class source file.
*
* Contains the source code of the Question class.
*
* PHP version 7.2
*
* @package Danmaku\Console\Question
* @author Marvin Schreurs <fristi@danmaku.moe>
*/
namespace Danmaku\Console\Question;
use Symfony\Component\Console\Question\Question as SymfonyQuestion;
/**
* Class Question
*
* @package Danmaku\Console\Question
* @author Marvin Schreurs <fristi@danmaku.moe>
*/
class Question extends SymfonyQuestion
{
protected function isAssoc($array)
{
return true;
}
}

View file

@ -1,546 +0,0 @@
<?php
/**
* DefaultOutputWriter class source file.
*
* Contains the source code of the DefaultOutputWriter class.
*
* PHP version 7.2
*
* @package Danmaku\Console\Writer
* @author Marvin Schreurs <fristi@danmaku.moe>
*/
namespace Danmaku\Console\Writer;
use Danmaku\LaravelConsoleWriter\Console\Helper\QuestionHelper;
use Danmaku\LaravelConsoleWriter\Console\Question\ChoiceQuestion;
use Danmaku\LaravelConsoleWriter\Console\Question\ConfirmationQuestion;
use Danmaku\LaravelConsoleWriter\Console\Question\Question;
use Symfony\Component\Console\Exception\RuntimeException;
use Symfony\Component\Console\Formatter\OutputFormatter;
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
use Symfony\Component\Console\Helper\Helper;
use Symfony\Component\Console\Helper\ProgressBar;
use Symfony\Component\Console\Helper\SymfonyQuestionHelper;
use Symfony\Component\Console\Helper\Table;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\BufferedOutput;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\Question as SymfonyQuestion;
use Symfony\Component\Console\Terminal;
/**
* Class DefaultOutputWriter
*
* @package Danmaku\Console\Writer
* @author Marvin Schreurs <fristi@danmaku.moe>
*/
class DefaultOutputWriter implements OutputWriterInterface
{
/**
* Console input interface.
*
* @var InputInterface $input
*/
protected $input;
/**
* Console output interface.
*
* @var OutputInterface $output
*/
protected $output;
/**
* ProgressBar instance.
*
* @var ProgressBar $progressBar
*/
protected $progressBar;
/**
* Output buffer.
*
* @var BufferedOutput $bufferedOutput
*/
protected $bufferedOutput;
/**
* Helper instance for handling questions.
*
* @var SymfonyQuestionHelper $questionHelper
*/
protected $questionHelper;
/**
* Calculated line length that will be used in the console.
*
* @var int $lineLength
*/
protected $lineLength;
/**
* The list of available output styles with their settings.
*
* @var array $styles
*/
protected $styles = [
'comment' => ['white', 'black'],
'info' => ['cyan', 'black'],
'warning' => ['yellow', 'black'],
'danger' => ['red', 'black'],
'success' => ['green', 'black'],
'debug' => ['black', 'white'],
'comment-bg' => ['black', 'white'],
'info-bg' => ['black', 'cyan'],
'warning-bg' => ['black', 'yellow'],
'danger-bg' => ['white', 'red'],
'success-bg' => ['black', 'green'],
'debug-bg' => ['black', 'white'],
'title' => ['green', 'black'],
'section' => ['white', 'black'],
'question'=> ['green', 'black'],
'answer' => ['yellow', 'black'],
'choice' => ['yellow', 'black'],
];
/**
* The maximum length of lines to use for console ouput.
*
* @var int $maxLineLength
*/
protected $maxLineLength = 120;
/**
* BaseOutputStyler constructor.
*
* @param InputInterface $input
* @param OutputInterface $output
*/
public function __construct(InputInterface $input, OutputInterface $output)
{
//Set input and output interface
$this->input = $input;
$this->output = $output;
//Set additional output styles
foreach($this->styles as $style => $values) {
$formatter = new OutputFormatterStyle($values[0], $values[1]);
$output->getFormatter()->setStyle($style, $formatter);
}
$this->bufferedOutput = new BufferedOutput($output->getVerbosity(), false, clone $output->getFormatter());
// Windows cmd wraps lines as soon as the terminal width is reached, whether there are following chars or not.
$width = (new Terminal())->getWidth() ?: $this->maxLineLength;
$this->lineLength = min($width - (int) (\DIRECTORY_SEPARATOR === '\\'), $this->maxLineLength);
}
public function title(string $title): void
{
$this->autoPrependBlock();
$this->writeln([
sprintf('<title>%s</>', OutputFormatter::escapeTrailingBackslash($title)),
sprintf('<title>%s</>', str_repeat('=', Helper::strlenWithoutDecoration($this->output->getFormatter(), $title))),
]);
$this->newLine();
}
public function section(string $heading): void
{
$this->autoPrependBlock();
$this->writeln([
sprintf('<section>%s</>', OutputFormatter::escapeTrailingBackslash($heading)),
sprintf('<section>%s</>', str_repeat('-', Helper::strlenWithoutDecoration($this->output->getFormatter(), $heading))),
]);
$this->newLine();
}
public function table(array $headers, array $rows): void
{
$style = clone Table::getStyleDefinition('symfony-style-guide');
$style->setCellHeaderFormat('<info>%s</info>');
$table = new Table($this->output);
$table->setHeaders($headers);
$table->setRows($rows);
$table->setStyle($style);
$table->render();
$this->newLine();
}
public function list(array $items, bool $dictionary = false): void
{
$this->autoPrependText();
if($dictionary) {
$width = $this->_getColumnWidth(array_keys($items));
$this->newLine();
foreach($items as $item => $description) {
//Format line
$spacingWidth = $width - Helper::strlen($item);
$line = sprintf(' %s%s%s', $item, str_repeat(' ', $spacingWidth), $description);
$this->writeln($line);
}
} else {
$items = array_map(function ($item) {
return sprintf(' * %s', $item);
}, $items);
$this->writeln($items);
}
$this->newLine();
}
public function text($message, string $style = null, $verbosity = OutputInterface::VERBOSITY_NORMAL): void
{
$this->autoPrependText();
$messages = \is_array($message) ? array_values($message) : [$message];
foreach ($messages as $message) {
if(!empty($style)){
$message = sprintf(" <$style>%s</>", $message);
}
$this->writeln(sprintf(' %s', $message), $verbosity);
}
}
public function success($message, bool $padding = false): void
{
if(!$this->output->isQuiet()) {
$style = $padding ? 'success-bg' : 'success';
$this->block($message, 'OK', $style, ' ', $padding);
}
}
public function error($message, bool $padding = false): void
{
if(!$this->output->isQuiet()) {
$style = $padding ? 'danger-bg' : 'danger';
$this->block($message, 'ERROR', $style, ' ', $padding);
}
}
public function warning($message, bool $padding = false): void
{
if(!$this->output->isQuiet()) {
$style = $padding ? 'warning-bg' : 'warning';
$this->block($message, 'WARN', $style, ' ', $padding);
}
}
public function comment($message, bool $padding = false): void
{
if($this->output->isVeryVerbose()) {
$style = $padding ? 'comment-bg' : 'comment';
$this->block($message, null, $style, " // ", $padding, false);
}
}
public function info($message, bool $padding = false): void
{
if($this->output->isVeryVerbose()) {
$style = $padding ? 'info-bg' : 'info';
$this->block($message, 'INFO', $style, ' ', $padding);
}
}
public function debug($message, bool $padding = false): void
{
if($this->output->isDebug()) {
$style = $padding ? 'debug-bg' : 'debug';
$this->block($message, 'DEBUG', $style, ' ', $padding);
}
}
public function exception(\Throwable $throwable, string $message = null): void
{
$this->error([
$message ?? 'An exception has occurred!',
sprintf('In %s on line %s:', $throwable->getFile(), $throwable->getLine()),
$throwable->getMessage()
], true);
}
public function ask(string $question, $default = null, $validator = null)
{
$question = new Question($question, $default);
$question->setValidator($validator);
return $this->askQuestion($question);
}
public function password(string $question, $validator = null)
{
$question = new Question($question);
$question->setHidden(true);
$question->setValidator($validator);
return $this->askQuestion($question);
}
public function confirm(string $question, $default = true): bool
{
return $this->askQuestion(new ConfirmationQuestion($question, $default));
}
public function choice(string $question, array $choices, $default = null)
{
return $this->askQuestion(new ChoiceQuestion($question, $choices, $default));
}
public function startProgress(int $max = 0): void
{
$this->progressBar = $this->createProgress($max);
$this->progressBar->start();
}
public function advanceProgress(int $step = 1): void
{
$this->getProgressBar()->advance($step);
}
public function finishProgress(): void
{
$this->getProgressBar()->finish();
$this->newLine(2);
$this->progressBar = null;
}
public function createProgress(int $max = 0): ProgressBar
{
$progressBar = new ProgressBar($this->output, $max);
$progressBar->setBarCharacter('<success>=</>');
$progressBar->setEmptyBarCharacter('<danger> </>');
$progressBar->setProgressCharacter('<warning>></>');
$progressBar->setBarWidth(60);
$progressBar->setFormat(
" %status%\n %current%/%max% [%bar%] %percent:3s%%\n 🏁 %estimated:-50s% %memory:20s%"
);
return $progressBar;
}
public function newLine($count = 1): void
{
$this->output->write(str_repeat(PHP_EOL, $count));
$this->bufferedOutput->write(str_repeat("\n", $count));
}
/**
* @param $messages
* @param int $type
*/
protected function writeln($messages, $type = OutputInterface::OUTPUT_NORMAL)
{
if (!is_iterable($messages)) {
$messages = [$messages];
}
foreach ($messages as $message) {
$this->output->writeln($message, $type);
$this->writeBuffer($message, true, $type);
}
}
/**
* @param $messages
* @param bool $newline
* @param int $type
*/
protected function write($messages, $newline = false, $type = OutputInterface::OUTPUT_NORMAL)
{
if (!is_iterable($messages)) {
$messages = [$messages];
}
foreach ($messages as $message) {
$this->output->write($message, $newline, $type);
$this->writeBuffer($message, $newline, $type);
}
}
/**
* @param SymfonyQuestion $question
* @return mixed
* @throws \Exception
*/
protected function askQuestion(SymfonyQuestion $question)
{
if ($this->input->isInteractive()) {
$this->autoPrependBlock();
}
if (!$this->questionHelper) {
$this->questionHelper = new QuestionHelper();
}
$answer = $this->questionHelper->ask($this->input, $this->output, $question);
if ($this->input->isInteractive()) {
$this->newLine();
$this->bufferedOutput->write("\n");
}
return $answer;
}
/**
* Prepend a new line for an upcoming block.
*
* @return void
*/
protected function autoPrependBlock(): void
{
$chars = substr(str_replace(PHP_EOL, "\n", $this->bufferedOutput->fetch()), -2);
if (!isset($chars[0])) {
$this->newLine(); //empty history, so we should start with a new line.
return;
}
//Prepend new line for each non LF chars (This means no blank line was output before)
$this->newLine(2 - substr_count($chars, "\n"));
}
/**
* Prepend a new line for upcoming text.
*
* @return void
*/
protected function autoPrependText(): void
{
$fetched = $this->bufferedOutput->fetch();
//Prepend new line if last char isn't EOL:
if ("\n" !== substr($fetched, -1)) {
$this->newLine();
}
}
/**
* @param string $message
* @param bool $newLine
* @param int $type
*/
protected function writeBuffer(string $message, bool $newLine, int $type): void
{
// We need to know if the two last chars are PHP_EOL
// Preserve the last 4 chars inserted (PHP_EOL on windows is two chars) in the history buffer
$this->bufferedOutput->write(substr($message, -4), $newLine, $type);
}
/**
* Formats a message as a block of text.
*
* @param string|array $messages The message to write in the block
* @param string|null $type The block type (added in [] on first line)
* @param string|null $style The style to apply to the whole block
* @param string $prefix The prefix for the block
* @param bool $padding Whether to add vertical padding
* @param bool $escape Whether to escape the message
* @param int $verbosity
*/
protected function block($messages, $type = null, $style = null, $prefix = ' ', $padding = false, $escape = true, $verbosity = OutputInterface::OUTPUT_NORMAL)
{
$messages = \is_array($messages) ? array_values($messages) : [$messages];
if($padding) {
$this->autoPrependBlock();
} else {
$this->autoPrependText();
}
$this->writeln($this->createBlock($messages, $type, $style, $prefix, $padding, $escape), $verbosity);
if($padding) {
$this->newLine();
}
}
/**
* Format a set of messages as a block.
*
* @param iterable $messages
* @param string|null $type
* @param string|null $style
* @param string $prefix
* @param bool $padding
* @param bool $escape
* @return array
*/
private function createBlock(iterable $messages, string $type = null, string $style = null, string $prefix = ' ', bool $padding = false, bool $escape = false)
{
$indentLength = 0;
$prefixLength = Helper::strlenWithoutDecoration($this->output->getFormatter(), $prefix);
$lines = [];
if (null !== $type) {
$type = sprintf('[%s] ', $type);
$indentLength = \strlen($type);
$lineIndentation = str_repeat(' ', $indentLength);
}
// wrap and add newlines for each element
foreach ($messages as $key => $message) {
if ($escape) {
$message = OutputFormatter::escape($message);
}
$lines = array_merge($lines, explode(PHP_EOL, wordwrap($message, $this->lineLength - $prefixLength - $indentLength, PHP_EOL, true)));
if (\count($messages) > 1 && $key < \count($messages) - 1) {
$lines[] = '';
}
}
$firstLineIndex = 0;
if ($padding && $this->output->isDecorated()) {
$firstLineIndex = 1;
array_unshift($lines, '');
$lines[] = '';
}
foreach ($lines as $i => &$line) {
if (null !== $type) {
$line = $firstLineIndex === $i ? $type.$line : $lineIndentation.$line;
}
$line = $prefix.$line;
$line .= str_repeat(' ', $this->lineLength - Helper::strlenWithoutDecoration($this->output->getFormatter(), $line));
if ($style) {
$line = sprintf('<%s>%s</>', $style, $line);
}
}
return $lines;
}
protected function getProgressBar(): ProgressBar
{
if (!$this->progressBar) {
throw new RuntimeException('The ProgressBar is not started.');
}
return $this->progressBar;
}
/**
* Calculate the column width that fits all items.
*
* @param array $items
* @return int
*/
private function _getColumnWidth(array $items): int
{
$widths = [];
foreach ($items as $item) {
$widths[] = Helper::strlen($item);
}
return $widths ? max($widths) + 2 : 0;
}
}

View file

@ -1,206 +0,0 @@
<?php
/**
* OutputWriterInterface interface source file.
*
* Contains the source code of the OutputWriterInterface interface.
*
* PHP version 7.2
*
* @package Danmaku\Console\Writer
* @author Marvin Schreurs <fristi@danmaku.moe>
*/
namespace Danmaku\Console\Writer;
use Symfony\Component\Console\Helper\ProgressBar;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Interface OutputWriterInterface
*
* @package Danmaku\Console\Writer
* @author Marvin Schreurs <fristi@danmaku.moe>
*/
interface OutputWriterInterface
{
/**
* Formats a command title.
*
* @param string $title The title text to display.
* @return void
*/
public function title(string $title): void;
/**
* Formats a command section.
*
* @param string $heading The section heading to display.
* @return void
*/
public function section(string $heading): void;
/**
* Formats a table.
*
* @param array $headers A list of column headers.
* @param array $rows A list of rows with cells.
* @return void
*/
public function table(array $headers, array $rows): void;
/**
* Formats a list.
*
* @param array $items A list of items.
* @param bool $dictionary Print the list as key/value pairs.
* @return void
*/
public function list(array $items, bool $dictionary = false): void;
/**
* Writes an unformatted line of text.
*
* @param string|array $message Message to display.
* @param string|null $style Style to apply to the message.
* @param int $verbosity Verbosity level at which this message should be printed.
* @return void
*/
public function text($message, string $style = null, $verbosity = OutputInterface::VERBOSITY_NORMAL): void;
/**
* Formats a success message.
*
* @param string|array $message Message to display.
* @param bool $padding Render the message in a padded box.
* @return void
*/
public function success($message, bool $padding = false): void;
/**
* Formats an error message.
*
* @param string|array $message Message to display.
* @param bool $padding Render the message in a padded box.
* @return void
*/
public function error($message, bool $padding = false): void;
/**
* Formats a warning message.
*
* @param string|array $message Message to display.
* @param bool $padding Render the message in a padded box.
* @return void
*/
public function warning($message, bool $padding = false): void;
/**
* Formats a comment message.
*
* @param string|array $message Message to display.
* @param bool $padding Render the message in a padded box.
* @return void
*/
public function comment($message, bool $padding = false): void;
/**
* Formats an information message.
*
* @param string|array $message Message to display.
* @param bool $padding Render the message in a padded box.
* @return void
*/
public function info($message, bool $padding = false): void;
/**
* Formats a debug message.
*
* @param string|array $message Message to display.
* @param bool $padding Render the message in a padded box.
* @return void
*/
public function debug($message, bool $padding = false): void;
/**
* Formats an exception.
*
* @param \Throwable $throwable A throwable instance to report.
* @param string|null $message Message to display with the exception.
* @return void
*/
public function exception(\Throwable $throwable, string $message = null): void;
/**
* Asks the user for input.
*
* @param string $question Question to display.
* @param mixed|null $default Default value for input.
* @param callable|null $validator Callable used to validate input.
* @return mixed The user's input.
*/
public function ask(string $question, $default = null, $validator = null);
/**
* Ask the user for input, the input will be hidden.
*
* @param string $question Question to display.
* @param callable|null $validator Callable used to validate input.
* @return mixed The user's input.
*/
public function password(string $question, $validator = null);
/**
* Ask the user for confirmation (yes or no).
*
* @param string $question Question to display.
* @param bool $default Default value for input.
* @return bool The user's input.
*/
public function confirm(string $question, $default = true): bool;
/**
* Ask the user for input using a list of choices.
*
* @param string $question Question to display.
* @param array $choices List of choices.
* @param mixed|null $default Default value for input.
* @return mixed The user's input.
*/
public function choice(string $question, array $choices, $default = null);
/**
* Start a progressbar.
*
* @param int $max Maximum steps (0 if unknown).
*/
public function startProgress(int $max = 0): void;
/**
* Advance the progressbar by the given number of steps.
*
* @param int $step Amount of steps to advance.
*/
public function advanceProgress(int $step = 1): void;
/**
* Stop the progressbar.
*/
public function finishProgress(): void;
/**
* Create a ProgressBar instance.
*
* @param int $max Maximum steps (0 if unknown).
* @return ProgressBar The ProgressBar instance.
*/
public function createProgress(int $max = 0): ProgressBar;
/**
* Add newlines.
*
* @param int $count
* @return void
*/
public function newLine(int $count = 1): void;
}