Skip to content

Commit 4196619

Browse files
committed
feature #14057 [RFC][Console] Added console style guide helpers (v2) (kbond)
This PR was squashed before being merged into the 2.7 branch (closes #14057). Discussion ---------- [RFC][Console] Added console style guide helpers (v2) *(Rebased to 2.7)* | Q | A | ------------- | --- | Bug fix? | no | New feature? | yes | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | #12014, #12035, symfony/symfony-docs#4265 | License | MIT | Doc PR | todo ## Proposed API ### Code ```php // Symfony command protected function execute(InputInterface $input, OutputInterface $output) { $output = new SymfonyStyle($output); $output->title('Lorem Ipsum Dolor Sit Amet'); $output->text(array( 'Duis aute irure dolor in reprehenderit in voluptate velit esse', 'cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat' )); $output->ln(); $output->table(array('Name', 'Method', 'Scheme', 'Host', 'Path'), array( array('admin_post_new', 'ANY', 'ANY', 'ANY', '/admin/post/new'), array('admin_post_show', 'GET', 'ANY', 'ANY', '/admin/post/{id}'), array('admin_post_edit', 'ANY', 'ANY', 'ANY', '/admin/post/{id}/edit'), array('admin_post_delete', 'DELETE', 'ANY', 'ANY', '/admin/post/{id}'), )); $output->caution(array( 'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris. Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris.', 'foo' )); $output->section('Consectetur Adipisicing Elit Sed Do Eiusmod'); $output->listing(array( 'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod, tempor incididunt ut labore et dolore magna aliqua.', 'Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo.', 'Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.' )); $customValidator = function ($value) { if ($value == 'foo') { throw new \Exception('cannot be foo'); } return $value; }; // hidden question $output->note($output->askHidden('Hidden question')); // choice questions $output->note($output->choice('Choice question no default', array('choice1', 'choice2'))); $output->note($output->choice('Choice question with default', array('choice1', 'choice2'), 'choice1')); // confirmation questions $output->note($output->confirm('Confirmation with yes default', true) ? 'yes' : 'no'); $output->note($output->confirm('Confirmation with no default', false) ? 'yes' : 'no'); // standard questions $output->note($output->ask('Question no default')); $output->note($output->ask('Question no default and custom validator', null, $customValidator)); $output->note($output->ask('Question with default', 'default')); $output->note($output->ask('Question with default and custom validator', 'default', $customValidator)); $output->note('Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat.'); $output->success('Lorem ipsum dolor sit amet, consectetur adipisicing elit'); $output->error('Duis aute irure dolor in reprehenderit in voluptate velit esse.'); $output->warning('Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi.'); $output->progressStart(100); for ($i = 0; $i < 100; $i++) { usleep(10000); $output->progressAdvance(); } $output->progressFinish(); } ``` ### Output ``` $ php app/console command Lorem Ipsum Dolor Sit Amet ========================== // Duis aute irure dolor in reprehenderit in voluptate velit esse // cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat ------------------- -------- -------- ------ ----------------------- Name Method Scheme Host Path ------------------- -------- -------- ------ ----------------------- admin_post_new ANY ANY ANY /admin/post/new admin_post_show GET ANY ANY /admin/post/{id} admin_post_edit ANY ANY ANY /admin/post/{id}/edit admin_post_delete DELETE ANY ANY /admin/post/{id} ------------------- -------- -------- ------ ----------------------- ! [CAUTION] Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et ! dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris. Lorem ipsum dolor sit amet, ! consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim ! veniam, quis nostrud exercitation ullamco laboris. ! ! foo Consectetur Adipisicing Elit Sed Do Eiusmod ------------------------------------------- * Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod, tempor incididunt ut labore et dolore magna aliqua. * Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo. * Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Hidden question: > <f><o><o><enter> ! [NOTE] foo Choice question no default: [0] choice1 [1] choice2 > <enter> [ERROR] Value "" is invalid Choice question no default: [0] choice1 [1] choice2 > 0<enter> ! [NOTE] choice1 Choice question with default [choice1]: [0] choice1 [1] choice2 > 1<enter> ! [NOTE] choice2 Confirmation with yes default (yes/no) [yes]: > <enter> ! [NOTE] yes Confirmation with no default (yes/no) [no]: > <enter> ! [NOTE] no Question no default: > <enter> [ERROR] A value is required. Question no default: > foo<enter> ! [NOTE] foo Question no default and custom validator: > foo<enter> [ERROR] cannot be foo Question no default and custom validator: > <enter> [ERROR] A value is required. Question no default and custom validator: > foo<enter> [ERROR] cannot be foo Question no default and custom validator: > bar<enter> ! [NOTE] bar Question with default [default]: > <enter> ! [NOTE] default Question with default and custom validator [default]: > <enter> ! [NOTE] default ! [NOTE] Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. ! Excepteur sint occaecat cupidatat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu ! fugiat nulla pariatur. Excepteur sint occaecat cupidatat. Duis aute irure dolor in reprehenderit in voluptate velit ! esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat. [OK] Lorem ipsum dolor sit amet, consectetur adipisicing elit [ERROR] Duis aute irure dolor in reprehenderit in voluptate velit esse. [WARNING] Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi. 100/100 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100% ``` ### Screenshots ![screen1](https://cloud.githubusercontent.com/assets/127811/4507077/53bc009c-4b09-11e4-937c-44fe7fe30dc0.png) ![screen2](https://cloud.githubusercontent.com/assets/127811/4507078/53bf982e-4b09-11e4-8b5a-8c44c20ae4d9.png) ![screen](https://cloud.githubusercontent.com/assets/127811/6848451/b2e64848-d3a3-11e4-9916-43bd377684ca.png) Commits ------- 96b4210 [RFC][Console] Added console style guide helpers (v2)
2 parents acc66bf + 104104c commit 4196619

File tree

6 files changed

+747
-24
lines changed

6 files changed

+747
-24
lines changed

Helper/QuestionHelper.php

Lines changed: 47 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -109,25 +109,11 @@ public function getName()
109109
*/
110110
public function doAsk(OutputInterface $output, Question $question)
111111
{
112-
$inputStream = $this->inputStream ?: STDIN;
113-
114-
$message = $question->getQuestion();
115-
if ($question instanceof ChoiceQuestion) {
116-
$width = max(array_map('strlen', array_keys($question->getChoices())));
117-
118-
$messages = (array) $question->getQuestion();
119-
foreach ($question->getChoices() as $key => $value) {
120-
$messages[] = sprintf(" [<info>%-${width}s</info>] %s", $key, $value);
121-
}
122-
123-
$output->writeln($messages);
124-
125-
$message = $question->getPrompt();
126-
}
127-
128-
$output->write($message);
112+
$this->writePrompt($output, $question);
129113

114+
$inputStream = $this->inputStream ?: STDIN;
130115
$autocomplete = $question->getAutocompleterValues();
116+
131117
if (null === $autocomplete || !$this->hasSttyAvailable()) {
132118
$ret = false;
133119
if ($question->isHidden()) {
@@ -160,6 +146,49 @@ public function doAsk(OutputInterface $output, Question $question)
160146
return $ret;
161147
}
162148

149+
/**
150+
* Outputs the question prompt.
151+
*
152+
* @param OutputInterface $output
153+
* @param Question $question
154+
*/
155+
protected function writePrompt(OutputInterface $output, Question $question)
156+
{
157+
$message = $question->getQuestion();
158+
159+
if ($question instanceof ChoiceQuestion) {
160+
$width = max(array_map('strlen', array_keys($question->getChoices())));
161+
162+
$messages = (array) $question->getQuestion();
163+
foreach ($question->getChoices() as $key => $value) {
164+
$messages[] = sprintf(" [<info>%-${width}s</info>] %s", $key, $value);
165+
}
166+
167+
$output->writeln($messages);
168+
169+
$message = $question->getPrompt();
170+
}
171+
172+
$output->write($message);
173+
}
174+
175+
/**
176+
* Outputs an error message.
177+
*
178+
* @param OutputInterface $output
179+
* @param \Exception $error
180+
*/
181+
protected function writeError(OutputInterface $output, \Exception $error)
182+
{
183+
if (null !== $this->getHelperSet() && $this->getHelperSet()->has('formatter')) {
184+
$message = $this->getHelperSet()->get('formatter')->formatBlock($error->getMessage(), 'error');
185+
} else {
186+
$message = '<error>'.$error->getMessage().'</error>';
187+
}
188+
189+
$output->writeln($message);
190+
}
191+
163192
/**
164193
* Autocompletes a question.
165194
*
@@ -355,13 +384,7 @@ private function validateAttempts($interviewer, OutputInterface $output, Questio
355384
$attempts = $question->getMaxAttempts();
356385
while (null === $attempts || $attempts--) {
357386
if (null !== $error) {
358-
if (null !== $this->getHelperSet() && $this->getHelperSet()->has('formatter')) {
359-
$message = $this->getHelperSet()->get('formatter')->formatBlock($error->getMessage(), 'error');
360-
} else {
361-
$message = '<error>'.$error->getMessage().'</error>';
362-
}
363-
364-
$output->writeln($message);
387+
$this->writeError($output, $error);
365388
}
366389

367390
try {

Helper/SymfonyQuestionHelper.php

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Console\Helper;
13+
14+
use Symfony\Component\Console\Input\InputInterface;
15+
use Symfony\Component\Console\Output\OutputInterface;
16+
use Symfony\Component\Console\Question\ChoiceQuestion;
17+
use Symfony\Component\Console\Question\ConfirmationQuestion;
18+
use Symfony\Component\Console\Question\Question;
19+
use Symfony\Component\Console\Style\SymfonyStyle;
20+
21+
/**
22+
* Symfony Style Guide compliant question helper.
23+
*
24+
* @author Kevin Bond <[email protected]>
25+
*/
26+
class SymfonyQuestionHelper extends QuestionHelper
27+
{
28+
/**
29+
* {@inheritdoc}
30+
*/
31+
public function ask(InputInterface $input, OutputInterface $output, Question $question)
32+
{
33+
$validator = $question->getValidator();
34+
$question->setValidator(function ($value) use ($validator) {
35+
if (null !== $validator && is_callable($validator)) {
36+
$value = $validator($value);
37+
}
38+
39+
// make required
40+
if (!is_array($value) && !is_bool($value) && 0 === strlen($value)) {
41+
throw new \Exception('A value is required.');
42+
}
43+
44+
return $value;
45+
});
46+
47+
return parent::ask($input, $output, $question);
48+
}
49+
50+
/**
51+
* {@inheritdoc}
52+
*/
53+
protected function writePrompt(OutputInterface $output, Question $question)
54+
{
55+
$text = $question->getQuestion();
56+
$default = $question->getDefault();
57+
58+
switch (true) {
59+
case null === $default:
60+
$text = sprintf(' <info>%s</info>:', $text);
61+
62+
break;
63+
64+
case $question instanceof ConfirmationQuestion:
65+
$text = sprintf(' <info>%s (yes/no)</info> [<comment>%s</comment>]:', $text, $default ? 'yes' : 'no');
66+
67+
break;
68+
69+
case $question instanceof ChoiceQuestion:
70+
$choices = $question->getChoices();
71+
$text = sprintf(' <info>%s</info> [<comment>%s</comment>]:', $text, $choices[$default]);
72+
73+
break;
74+
75+
default:
76+
$text = sprintf(' <info>%s</info> [<comment>%s</comment>]:', $text, $default);
77+
}
78+
79+
$output->writeln($text);
80+
81+
if ($question instanceof ChoiceQuestion) {
82+
$width = max(array_map('strlen', array_keys($question->getChoices())));
83+
84+
foreach ($question->getChoices() as $key => $value) {
85+
$output->writeln(sprintf(" [<comment>%-${width}s</comment>] %s", $key, $value));
86+
}
87+
}
88+
89+
$output->write(' > ');
90+
}
91+
92+
/**
93+
* {@inheritdoc}
94+
*/
95+
protected function writeError(OutputInterface $output, \Exception $error)
96+
{
97+
if ($output instanceof SymfonyStyle) {
98+
$output->newLine();
99+
$output->error($error->getMessage());
100+
101+
return;
102+
}
103+
104+
parent::writeError($output, $error);
105+
}
106+
}

Helper/Table.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -587,10 +587,19 @@ private static function initStyles()
587587
->setCellRowContentFormat('%s')
588588
;
589589

590+
$styleGuide = new TableStyle();
591+
$styleGuide
592+
->setHorizontalBorderChar('-')
593+
->setVerticalBorderChar(' ')
594+
->setCrossingChar(' ')
595+
->setCellHeaderFormat('%s')
596+
;
597+
590598
return array(
591599
'default' => new TableStyle(),
592600
'borderless' => $borderless,
593601
'compact' => $compact,
602+
'symfony-style-guide' => $styleGuide,
594603
);
595604
}
596605
}

Style/OutputStyle.php

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Console\Style;
13+
14+
use Symfony\Component\Console\Formatter\OutputFormatterInterface;
15+
use Symfony\Component\Console\Helper\ProgressBar;
16+
use Symfony\Component\Console\Output\OutputInterface;
17+
18+
/**
19+
* Decorates output to add console style guide helpers
20+
*
21+
* @author Kevin Bond <[email protected]>
22+
*/
23+
abstract class OutputStyle implements OutputInterface, StyleInterface
24+
{
25+
private $output;
26+
27+
/**
28+
* @param OutputInterface $output
29+
*/
30+
public function __construct(OutputInterface $output)
31+
{
32+
$this->output = $output;
33+
}
34+
35+
/**
36+
* {@inheritdoc}
37+
*/
38+
public function newLine($count = 1)
39+
{
40+
$this->output->write(str_repeat(PHP_EOL, $count));
41+
}
42+
43+
/**
44+
* @param int $max
45+
*
46+
* @return ProgressBar
47+
*/
48+
public function createProgressBar($max = 0)
49+
{
50+
return new ProgressBar($this->output, $max);
51+
}
52+
53+
/**
54+
* {@inheritdoc}
55+
*/
56+
public function write($messages, $newline = false, $type = self::OUTPUT_NORMAL)
57+
{
58+
$this->output->write($messages, $newline, $type);
59+
}
60+
61+
/**
62+
* {@inheritdoc}
63+
*/
64+
public function writeln($messages, $type = self::OUTPUT_NORMAL)
65+
{
66+
$this->output->writeln($messages, $type);
67+
}
68+
69+
/**
70+
* {@inheritdoc}
71+
*/
72+
public function setVerbosity($level)
73+
{
74+
$this->output->setVerbosity($level);
75+
}
76+
77+
/**
78+
* {@inheritdoc}
79+
*/
80+
public function getVerbosity()
81+
{
82+
return $this->output->getVerbosity();
83+
}
84+
85+
/**
86+
* {@inheritdoc}
87+
*/
88+
public function setDecorated($decorated)
89+
{
90+
$this->output->setDecorated($decorated);
91+
}
92+
93+
/**
94+
* {@inheritdoc}
95+
*/
96+
public function isDecorated()
97+
{
98+
return $this->output->isDecorated();
99+
}
100+
101+
/**
102+
* {@inheritdoc}
103+
*/
104+
public function setFormatter(OutputFormatterInterface $formatter)
105+
{
106+
$this->output->setFormatter($formatter);
107+
}
108+
109+
/**
110+
* {@inheritdoc}
111+
*/
112+
public function getFormatter()
113+
{
114+
return $this->output->getFormatter();
115+
}
116+
}

0 commit comments

Comments
 (0)