Skip to content

Commit 9dca9fa

Browse files
authored
Merge pull request #727 from hydephp/refactor-MakePublicationTagCommand
Refactor and add tests for MakePublicationTagCommand
2 parents 11fc176 + 6699dda commit 9dca9fa

File tree

7 files changed

+298
-46
lines changed

7 files changed

+298
-46
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Hyde\Console\Commands\Helpers;
6+
7+
use function array_shift;
8+
use function explode;
9+
use function fgets;
10+
use Hyde\Framework\Concerns\InvokableAction;
11+
use Illuminate\Support\Str;
12+
use function trim;
13+
14+
/**
15+
* Collects an array of lines from the standard input stream. Feed is terminated by a blank line.
16+
*
17+
* @todo Add dynamic support for detecting and using comma separated values?
18+
*/
19+
class InputStreamHandler extends InvokableAction
20+
{
21+
/** @internal Allows for mocking of the standard input stream */
22+
private static ?array $streamBuffer = null;
23+
24+
public function __invoke(): array
25+
{
26+
return $this->getLinesFromInputStream();
27+
}
28+
29+
protected function getLinesFromInputStream(): array
30+
{
31+
$lines = [];
32+
do {
33+
$line = Str::replace(["\n", "\r"], '', $this->readInputStream());
34+
if ($line === '') {
35+
break;
36+
}
37+
$lines[] = trim($line);
38+
} while (true);
39+
40+
return $lines;
41+
}
42+
43+
/** @codeCoverageIgnore Allows for mocking of the standard input stream */
44+
protected function readInputStream(): array|string|false
45+
{
46+
if (self::$streamBuffer) {
47+
return array_shift(self::$streamBuffer);
48+
}
49+
50+
return fgets(STDIN);
51+
}
52+
53+
/** @internal Allows for mocking of the standard input stream */
54+
public static function mockInput(string $input): void
55+
{
56+
self::$streamBuffer = explode("\n", $input);
57+
}
58+
}

packages/framework/src/Console/Commands/MakePublicationTagCommand.php

+70-24
Original file line numberDiff line numberDiff line change
@@ -4,55 +4,101 @@
44

55
namespace Hyde\Console\Commands;
66

7+
use function array_merge;
8+
use Hyde\Console\Commands\Helpers\InputStreamHandler;
79
use Hyde\Console\Commands\Interfaces\CommandHandleInterface;
810
use Hyde\Console\Concerns\ValidatingCommand;
11+
use Hyde\Facades\Filesystem;
912
use Hyde\Framework\Features\Publications\PublicationService;
13+
use Hyde\Framework\Services\DiscoveryService;
1014
use Hyde\Hyde;
11-
use Illuminate\Support\Str;
15+
use function implode;
1216
use LaravelZero\Framework\Commands\Command;
13-
use function Safe\file_put_contents;
17+
use RuntimeException;
1418
use function Safe\json_encode;
19+
use function sprintf;
1520

1621
/**
1722
* Hyde Command to create a new publication type.
1823
*
19-
* @see \Hyde\Framework\Testing\Feature\Commands\MakePublicationTypeCommandTest
24+
* @see \Hyde\Framework\Testing\Feature\Commands\MakePublicationTagCommandTest
2025
*/
2126
class MakePublicationTagCommand extends ValidatingCommand implements CommandHandleInterface
2227
{
2328
/** @var string */
24-
protected $signature = 'make:publicationTag';
29+
protected $signature = 'make:publicationTag {tagName? : The name of the tag to create}';
2530

2631
/** @var string */
2732
protected $description = 'Create a new publication type tag definition';
2833

29-
public function handle(): int
34+
protected array $tags;
35+
protected string $tagName;
36+
37+
public function safeHandle(): int
3038
{
3139
$this->title('Creating a new Publication Type Tag!');
3240

33-
$filename = Hyde::pathToRelative('tags.json');
34-
$tags = PublicationService::getAllTags();
35-
$tagName = $this->askWithValidation('name', 'Tag name', ['required', 'string']);
36-
if (isset($tags[$tagName])) {
37-
$this->output->error("Tag [$tagName] already exists");
41+
$this->getTagName();
3842

39-
return Command::FAILURE;
40-
}
43+
$this->validateTagName();
4144

42-
$lines = [];
43-
$this->output->writeln('<bg=magenta;fg=white>Enter the tag values (end with an empty line):</>');
44-
do {
45-
$line = Str::replace(["\n", "\r"], '', fgets(STDIN));
46-
if ($line === '') {
47-
break;
48-
}
49-
$lines[] = trim($line);
50-
} while (true);
51-
$tags[$tagName] = $lines;
45+
$this->collectTags();
5246

53-
$this->output->writeln(sprintf('Saving tag data to [%s]', $filename));
54-
file_put_contents($filename, json_encode($tags, JSON_PRETTY_PRINT));
47+
$this->printSelectionInformation();
48+
49+
$this->saveTagsToDisk();
5550

5651
return Command::SUCCESS;
5752
}
53+
54+
protected function getTagName(): void
55+
{
56+
$this->tagName = $this->getTagNameFromArgument($this->argument('tagName'))
57+
?? $this->askWithValidation('name', 'Tag name', ['required', 'string']);
58+
}
59+
60+
protected function getTagNameFromArgument(?string $value): ?string
61+
{
62+
if ($value) {
63+
$this->infoComment('Using tag name', $value, 'from command line argument');
64+
$this->newLine();
65+
66+
return $value;
67+
}
68+
69+
return null;
70+
}
71+
72+
protected function validateTagName(): void
73+
{
74+
if (PublicationService::getAllTags()->has($this->tagName)) {
75+
throw new RuntimeException("Tag [$this->tagName] already exists");
76+
}
77+
}
78+
79+
protected function collectTags(): void
80+
{
81+
$this->info('Enter the tag values: (end with an empty line)');
82+
$this->tags = [$this->tagName => InputStreamHandler::call()];
83+
}
84+
85+
protected function printSelectionInformation(): void
86+
{
87+
$this->line('Adding the following tags:');
88+
foreach ($this->tags as $tag => $values) {
89+
$this->line(sprintf(' <comment>%s</comment>: %s', $tag, implode(', ', $values)));
90+
}
91+
$this->newLine();
92+
}
93+
94+
protected function saveTagsToDisk(): void
95+
{
96+
$this->infoComment('Saving tag data to',
97+
DiscoveryService::createClickableFilepath(Hyde::path('tags.json'))
98+
);
99+
100+
Filesystem::putContents('tags.json', json_encode(array_merge(
101+
PublicationService::getAllTags()->toArray(), $this->tags
102+
), JSON_PRETTY_PRINT));
103+
}
58104
}

packages/framework/src/Console/Concerns/ValidatingCommand.php

+8
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,14 @@ public function handleException(Exception $exception, ?string $file = null, ?int
101101
return Command::FAILURE;
102102
}
103103

104+
/**
105+
* Write a nicely formatted and consistent message to the console. Using InfoComment for a lack of a better term.
106+
*/
107+
public function infoComment(string $info, string $comment, ?string $moreInfo = null): void
108+
{
109+
$this->line("<info>$info</info> [<comment>$comment</comment>]".($moreInfo ? " <info>$moreInfo</info>" : ''));
110+
}
111+
104112
protected function translate($name, string $error): string
105113
{
106114
return $this->makeReplacements($name, Str::after($error, 'validation.'), $this->getTranslationLines());

packages/framework/src/Framework/Features/Publications/PublicationService.php

+20-21
Original file line numberDiff line numberDiff line change
@@ -22,24 +22,6 @@
2222
*/
2323
class PublicationService
2424
{
25-
/**
26-
* Get all available/tags.
27-
*
28-
* @return \Rgasch\Collection\Collection
29-
*
30-
* @throws \Safe\Exceptions\FilesystemException
31-
* @throws \Safe\Exceptions\JsonException
32-
*/
33-
public static function getAllTags(): Collection
34-
{
35-
$filename = Hyde::pathToRelative('tags.json');
36-
if (! file_exists($filename)) {
37-
return Collection::create();
38-
}
39-
40-
return Collection::create(json_decode(file_get_contents($filename), true))->sortKeys();
41-
}
42-
4325
/**
4426
* Return a collection of all defined publication types, indexed by the directory name.
4527
*
@@ -76,19 +58,36 @@ public static function getMediaForPubType(PublicationType $pubType): Collection
7658
});
7759
}
7860

61+
/**
62+
* Get all available tags.
63+
*
64+
* @return \Rgasch\Collection\Collection
65+
*
66+
* @throws \Safe\Exceptions\FilesystemException
67+
* @throws \Safe\Exceptions\JsonException
68+
*/
69+
public static function getAllTags(): Collection
70+
{
71+
$filename = Hyde::pathToRelative('tags.json');
72+
if (! file_exists($filename)) {
73+
return Collection::create();
74+
}
75+
76+
return Collection::create(json_decode(file_get_contents($filename), true))->sortKeys();
77+
}
78+
7979
/**
8080
* Get all values for a given tag name.
8181
*
8282
* @param string $tagName
83-
* @param \Hyde\Framework\Features\Publications\Models\PublicationType $publicationType
8483
* @return \Rgasch\Collection\Collection|null
8584
*
8685
* @throws \Safe\Exceptions\FilesystemException
8786
* @throws \Safe\Exceptions\JsonException
8887
*/
89-
public static function getValuesForTagName(string $tagName, PublicationType $publicationType): ?Collection
88+
public static function getValuesForTagName(string $tagName): ?Collection
9089
{
91-
$tags = self::getAllTags($publicationType);
90+
$tags = static::getAllTags();
9291
if ($tags->has($tagName)) {
9392
return $tags->$tagName;
9493
}

packages/framework/src/Framework/Services/DiscoveryService.php

+3
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,9 @@ public static function getModelSourceDirectory(string $model): string
9393

9494
/**
9595
* Create a filepath that can be opened in the browser from a terminal.
96+
*
97+
* @todo Add support for custom label?
98+
* @todo Add option to treat path as already validated so paths that are not created yet can be printed?
9699
*/
97100
public static function createClickableFilepath(string $filepath): string
98101
{
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Hyde\Framework\Testing\Feature\Commands;
6+
7+
use Hyde\Console\Commands\Helpers\InputStreamHandler;
8+
use Hyde\Hyde;
9+
use Hyde\Testing\TestCase;
10+
use function unlink;
11+
12+
/**
13+
* @covers \Hyde\Console\Commands\MakePublicationTagCommand
14+
* @covers \Hyde\Console\Commands\Helpers\InputStreamHandler {@todo Extract this to a separate test class}
15+
*/
16+
class MakePublicationTagCommandTest extends TestCase
17+
{
18+
protected function tearDown(): void
19+
{
20+
unlink(Hyde::path('tags.json'));
21+
22+
parent::tearDown();
23+
}
24+
25+
public function testCanCreateNewPublicationTag()
26+
{
27+
InputStreamHandler::mockInput("foo\nbar\nbaz\n");
28+
29+
$this->artisan('make:publicationTag')
30+
->expectsQuestion('Tag name', 'foo')
31+
->expectsOutput('Enter the tag values: (end with an empty line)')
32+
->expectsOutput('Adding the following tags:')
33+
->expectsOutput(' foo: foo, bar, baz')
34+
->expectsOutput('Saving tag data to ['.Hyde::path('tags.json').']')
35+
->assertExitCode(0);
36+
37+
$this->assertFileExists(Hyde::path('tags.json'));
38+
$this->assertSame(
39+
json_encode(['foo' => ['foo', 'bar', 'baz']], 128),
40+
file_get_contents(Hyde::path('tags.json'))
41+
);
42+
}
43+
44+
public function testCanCreateNewPublicationTagWithTagNameArgument()
45+
{
46+
InputStreamHandler::mockInput("foo\nbar\nbaz\n");
47+
48+
$this->artisan('make:publicationTag foo')
49+
->expectsOutput('Using tag name [foo] from command line argument')
50+
->expectsOutput('Enter the tag values: (end with an empty line)')
51+
->expectsOutput('Adding the following tags:')
52+
->expectsOutput(' foo: foo, bar, baz')
53+
->expectsOutput('Saving tag data to ['.Hyde::path('tags.json').']')
54+
->assertExitCode(0);
55+
56+
$this->assertFileExists(Hyde::path('tags.json'));
57+
$this->assertSame(
58+
json_encode(['foo' => ['foo', 'bar', 'baz']], 128),
59+
file_get_contents(Hyde::path('tags.json'))
60+
);
61+
}
62+
63+
public function testCommandFailsIfTagNameIsAlreadySet()
64+
{
65+
InputStreamHandler::mockInput("foo\nbar\nbaz\n");
66+
67+
$this->artisan('make:publicationTag foo')
68+
->assertExitCode(0);
69+
70+
InputStreamHandler::mockInput("foo\nbar\nbaz\n");
71+
72+
$this->artisan('make:publicationTag foo')
73+
->expectsOutput('Error: Tag [foo] already exists')
74+
->assertExitCode(1);
75+
}
76+
77+
public function testCanTerminateWithCarriageReturns()
78+
{
79+
InputStreamHandler::mockInput("foo\r\nbar\r\nbaz\r\n");
80+
81+
$this->artisan('make:publicationTag foo')->assertExitCode(0);
82+
}
83+
84+
public function testCanTerminateWithUnixEndings()
85+
{
86+
InputStreamHandler::mockInput("foo\nbar\nbaz\n");
87+
88+
$this->artisan('make:publicationTag foo')->assertExitCode(0);
89+
}
90+
}

0 commit comments

Comments
 (0)