Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update the make:publication command #786

Merged
merged 36 commits into from
Dec 29, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
7fb45b4
Fix publication type generator to properly specify the template files
caendesilva Dec 28, 2022
005b5e1
Format the question to match infoComment output
caendesilva Dec 28, 2022
c2bdb4e
Merge branch 'publications-feature' into publications-feature-refactors
caendesilva Dec 28, 2022
ddffdc0
Skip meta fields as those should not be filled in by user
caendesilva Dec 28, 2022
c2790fe
Remove unnecessary mock
caendesilva Dec 28, 2022
f4a5887
Extract method to cast validated boolean result to bool
caendesilva Dec 28, 2022
27c8b45
Remove the boolean rule from array
caendesilva Dec 28, 2022
64bde1f
Return null instead of false if the selection is empty
caendesilva Dec 28, 2022
b3303aa
Create a smarter boolean field input
caendesilva Dec 28, 2022
c93149a
Apply fixes from StyleCI
StyleCIBot Dec 28, 2022
6db9004
Add code comment
caendesilva Dec 28, 2022
7bf454f
Return null when retry count is exceeded to prevent infinite loop
caendesilva Dec 28, 2022
0f49e6c
Test with boolean input
caendesilva Dec 28, 2022
0088192
Deprecate helper method
caendesilva Dec 28, 2022
9945e96
Indent warning message empty collections
caendesilva Dec 28, 2022
fe7d71f
Colour warning prefix red to make it stand out more
caendesilva Dec 28, 2022
aa65b7a
Add message when skipping a field
caendesilva Dec 29, 2022
0aee650
Also consider empty string to be skipped, and return null
caendesilva Dec 29, 2022
6728d9f
Skip all fields that are empty
caendesilva Dec 29, 2022
03fba38
Skip skipped values when generating front matter
caendesilva Dec 29, 2022
25f024a
Apply fixes from StyleCI
StyleCIBot Dec 29, 2022
3c3f0f3
Skip skipped values when generating front matter
caendesilva Dec 29, 2022
b32277d
Merge branch 'publications-feature-refactors' of github.com:hydephp/d…
caendesilva Dec 29, 2022
8057884
Apply fixes from StyleCI
StyleCIBot Dec 29, 2022
4c3f465
Formatting
caendesilva Dec 29, 2022
b36f0e8
Update helper method name
caendesilva Dec 29, 2022
817afbe
Merge newline into output
caendesilva Dec 29, 2022
893e2df
Revert "Merge newline into output"
caendesilva Dec 29, 2022
1d712b3
Normalize output styles for messages to enter array and text input
caendesilva Dec 29, 2022
048ca2d
Get the validation rules from the field not the type
caendesilva Dec 29, 2022
c3be3f0
Test with custom validation rules
caendesilva Dec 29, 2022
be9d3db
Temporarily expect integer to be string until that is fixed
caendesilva Dec 29, 2022
803661d
Apply fixes from StyleCI
StyleCIBot Dec 29, 2022
0fcc271
Add tag to supress faulty inspection
caendesilva Dec 29, 2022
a894ed1
Normalize integers to be integers
caendesilva Dec 29, 2022
b8588ab
Revert "Temporarily expect integer to be string until that is fixed"
caendesilva Dec 29, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,8 @@ public static function mockInput(string $input): void
}

/** Format a consistent message that can be output to the console */
public static function formatMessage(string $name): string
public static function formatMessage(string $name, string $type = 'values'): string
{
return "Enter $name (end with an empty line)";
return "<info>Enter $type for field </>[<comment>$name</comment>] (end with an empty line)";
}
}
65 changes: 58 additions & 7 deletions packages/framework/src/Console/Commands/MakePublicationCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace Hyde\Console\Commands;

use function array_flip;
use Closure;
use Hyde\Console\Commands\Helpers\InputStreamHandler;
use Hyde\Console\Concerns\ValidatingCommand;
Expand All @@ -17,6 +18,7 @@
use function in_array;
use InvalidArgumentException;
use LaravelZero\Framework\Commands\Command;
use function str_starts_with;

/**
* Hyde Command to create a new publication for a given publication type.
Expand Down Expand Up @@ -102,27 +104,39 @@ protected function collectFieldData(): Collection

/** @var PublicationField $field */
foreach ($this->publicationType->getFields() as $field) {
if (str_starts_with($field->name, '__')) {
continue;
}
$this->newLine();
$data->put($field->name, $this->captureFieldInput($field));
}

return $data;
}

protected function captureFieldInput(PublicationField $field): string|array|null
protected function captureFieldInput(PublicationField $field): bool|string|array|null
{
return match ($field->type) {
$selection = match ($field->type) {
PublicationFieldTypes::Text => $this->captureTextFieldInput($field),
PublicationFieldTypes::Array => $this->captureArrayFieldInput($field),
PublicationFieldTypes::Image => $this->captureImageFieldInput($field),
PublicationFieldTypes::Tag => $this->captureTagFieldInput($field),
default => $this->askWithValidation($field->name, $field->name, $field->type->rules()),
PublicationFieldTypes::Boolean => $this->captureBooleanFieldInput($field),
default => $this->askWithValidation($field->name, "Enter data for field </>[<comment>$field->name</comment>]", $field->getValidationRules()->toArray()),
};

if (empty($selection)) {
$this->line("<fg=gray> > Skipping field $field->name</>");

return null;
}

return $selection;
}

protected function captureTextFieldInput(PublicationField $field): string
{
$this->line(InputStreamHandler::formatMessage($field->name));
$this->line(InputStreamHandler::formatMessage($field->name, 'lines'));

return implode("\n", InputStreamHandler::call());
}
Expand Down Expand Up @@ -160,13 +174,50 @@ protected function captureTagFieldInput(PublicationField $field): array|string|n

$this->tip('You can enter multiple tags separated by commas');

return $this->reloadableChoice($this->getTagValuesArrayClosure(),
return $this->reloadableChoice($this->getReloadableTagValuesArrayClosure(),
'Which tag would you like to use?',
'Reload tags.json',
true
);
}

/**
* @deprecated Will be refactored into a dedicated rule
*/
protected function captureBooleanFieldInput(PublicationField $field, $retryCount = 1): ?bool
{
// Return null when retry count is exceeded to prevent infinite loop
if ($retryCount > 30) {
return null;
}

// Since the Laravel validation rule for booleans doesn't accept the string input provided by the console,
// we need to do some logic of our own to support validating booleans through the console.

$rules = $field->type->rules();
$rules = array_flip($rules);
unset($rules['boolean']);
$rules = array_flip($rules);

$selection = $this->askWithValidation($field->name, "Enter data for field </>[<comment>$field->name</comment>]", $rules);

if (empty($selection)) {
return null;
}

$acceptable = ['true', 'false', true, false, 0, 1, '0', '1'];

// Strict parameter is needed as for some reason `in_array($selection, [true])` is always true no matter what the value of $selection is.
if (in_array($selection, $acceptable, true)) {
return (bool) $selection;
} else {
// Match the formatting of the standard Laravel validation error message.
$this->error("The $field->name field must be true or false.");

return $this->captureBooleanFieldInput($field, $retryCount + 1);
}
}

/** @return null */
protected function handleEmptyOptionsCollection(PublicationField $field, string $type, string $message)
{
Expand All @@ -175,7 +226,7 @@ protected function handleEmptyOptionsCollection(PublicationField $field, string
}

$this->newLine();
$this->warn("Warning: $message");
$this->warn(" <fg=red>Warning:</> $message");
if ($this->confirm('Would you like to skip this field?', true)) {
return null;
} else {
Expand All @@ -189,7 +240,7 @@ protected function tip(string $message): void
}

/** @return Closure<array<string>> */
protected function getTagValuesArrayClosure(): Closure
protected function getReloadableTagValuesArrayClosure(): Closure
{
return function (): array {
return PublicationService::getValuesForTagName($this->publicationType->getIdentifier())->toArray();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,13 +76,22 @@ protected function getMergedData(): array
protected function normalizeData(array $array): array
{
foreach ($array as $key => $value) {
if (empty($value)) {
unset($array[$key]);
continue;
}

$type = $this->pubType->getFields()->get($key);

if ($type->type === PublicationFieldTypes::Text) {
// In order to properly store text fields as block literals,
// we need to make sure they end with a newline.
$array[$key] = trim($value)."\n";
}

if ($type->type === PublicationFieldTypes::Integer) {
$array[$key] = (int) $value;
}
}

return $array;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ public function test_command_creates_publication()
->expectsOutputToContain('Creating a new publication!')
->expectsChoice('Which publication type would you like to create a publication item for?', 0, ['test-publication'])
->expectsOutput('Creating a new publication of type [test-publication]')
->expectsQuestion('Title', 'Hello World')
->expectsQuestion('Enter data for field </>[<comment>title</comment>]', 'Hello World')
->expectsOutput('Created file test-publication/hello-world.md')
->assertExitCode(0);

Expand All @@ -69,7 +69,7 @@ public function test_command_with_existing_publication()
$this->artisan('make:publication')
->expectsOutputToContain('Creating a new publication!')
->expectsChoice('Which publication type would you like to create a publication item for?', 0, ['test-publication'])
->expectsQuestion('Title', 'Hello World')
->expectsQuestion('Enter data for field </>[<comment>title</comment>]', 'Hello World')
->expectsOutput('Error: A publication already exists with the same canonical field value')
->expectsConfirmation('Do you wish to overwrite the existing file?')
->expectsOutput('Exiting without overwriting existing publication file!')
Expand All @@ -87,7 +87,7 @@ public function test_command_with_existing_publication_and_overwrite()
$this->artisan('make:publication')
->expectsOutputToContain('Creating a new publication!')
->expectsChoice('Which publication type would you like to create a publication item for?', 0, ['test-publication'])
->expectsQuestion('Title', 'Hello World')
->expectsQuestion('Enter data for field </>[<comment>title</comment>]', 'Hello World')
->expectsOutput('Error: A publication already exists with the same canonical field value')
->expectsConfirmation('Do you wish to overwrite the existing file?', 'yes')
->assertExitCode(0);
Expand All @@ -103,7 +103,7 @@ public function test_can_overwrite_existing_publication_by_passing_force_flag()
$this->artisan('make:publication', ['--force' => true])
->expectsOutputToContain('Creating a new publication!')
->expectsChoice('Which publication type would you like to create a publication item for?', 0, ['test-publication'])
->expectsQuestion('Title', 'Hello World')
->expectsQuestion('Enter data for field </>[<comment>title</comment>]', 'Hello World')
->assertExitCode(0);

$this->assertNotEquals('foo', file_get_contents(Hyde::path('test-publication/hello-world.md')));
Expand All @@ -115,7 +115,7 @@ public function test_command_with_publication_type_passed_as_argument()

$this->artisan('make:publication test-publication')
->expectsOutput('Creating a new publication of type [test-publication]')
->expectsQuestion('Title', 'Hello World')
->expectsQuestion('Enter data for field </>[<comment>title</comment>]', 'Hello World')
->expectsOutput('Created file test-publication/hello-world.md')
->assertExitCode(0);

Expand All @@ -135,7 +135,6 @@ public function test_command_with_invalid_publication_type_passed_as_argument()

public function test_command_with_schema_using_canonical_meta_field()
{
InputStreamHandler::mockInput("Foo\nBar");
$this->makeSchemaFile([
'canonicalField' => '__createdAt',
'fields' => [],
Expand All @@ -157,6 +156,24 @@ public function test_command_with_schema_using_canonical_meta_field()
MARKDOWN, file_get_contents(Hyde::path('test-publication/2022-01-01-000000.md')));
}

public function test_command_does_not_ask_user_to_fill_in_meta_fields()
{
$this->makeSchemaFile([
'canonicalField' => '__createdAt',
'fields' => [[
'type' => 'string',
'name' => '__createdAt',
]],
]);

$this->artisan('make:publication test-publication')
->doesntExpectOutput('Enter data for field </>[<comment>__createdAt</comment>]')
->doesntExpectOutputToContain('__createdAt')
->assertExitCode(0);

$this->assertDatedPublicationExists();
}

public function test_command_with_text_input()
{
InputStreamHandler::mockInput("Hello\nWorld");
Expand All @@ -178,6 +195,24 @@ public function test_command_with_text_input()
);
}

public function test_command_with_boolean_input()
{
$this->makeSchemaFile([
'canonicalField' => '__createdAt',
'fields' => [[
'type' => 'boolean',
'name' => 'published',
],
],
]);
$this->artisan('make:publication test-publication')
->expectsQuestion('Enter data for field </>[<comment>published</comment>]', 'true')
->assertExitCode(0);

$this->assertDatedPublicationExists();
$this->assertCreatedPublicationMatterEquals('published: true');
}

public function test_command_with_array_input()
{
InputStreamHandler::mockInput("First Tag\nSecond Tag\nThird Tag");
Expand Down Expand Up @@ -259,6 +294,7 @@ public function test_command_with_multiple_tag_inputs()
],
]);

/** @noinspection PhpParamsInspection as array is allowed by this method */
$this->artisan('make:publication test-publication')
->expectsQuestion('Which tag would you like to use?', ['foo', 'bar'])
->assertExitCode(0);
Expand All @@ -282,7 +318,7 @@ public function test_image_input_with_no_images()
]);

$this->artisan('make:publication test-publication')
->expectsOutput('Warning: No media files found in directory _media/test-publication/')
->expectsOutput(' Warning: No media files found in directory _media/test-publication/')
->expectsConfirmation('Would you like to skip this field?')
->expectsOutput('Error: Unable to locate any media files for this publication type')
->assertExitCode(1);
Expand All @@ -302,13 +338,22 @@ public function test_image_input_with_no_images_but_skips()
]);

$this->artisan('make:publication test-publication')
->expectsOutput('Warning: No media files found in directory _media/test-publication/')
->expectsOutput(' Warning: No media files found in directory _media/test-publication/')
->expectsConfirmation('Would you like to skip this field?', 'yes')
->doesntExpectOutput('Error: Unable to locate any media files for this publication type')
->assertExitCode(0);

$this->assertDatedPublicationExists();
$this->assertCreatedPublicationMatterEquals('image: null');
$this->assertEquals(
<<<'MARKDOWN'
---
__createdAt: 2022-01-01T00:00:00+00:00
---

## Write something awesome.


MARKDOWN, $this->getDatedPublicationContents());
}

public function test_tag_input_with_no_tags()
Expand All @@ -324,7 +369,7 @@ public function test_tag_input_with_no_tags()
]);

$this->artisan('make:publication test-publication')
->expectsOutput('Warning: No tags for this publication type found in tags.json')
->expectsOutput(' Warning: No tags for this publication type found in tags.json')
->expectsConfirmation('Would you like to skip this field?')
->expectsOutput('Error: Unable to locate any tags for this publication type')
->assertExitCode(1);
Expand All @@ -344,13 +389,22 @@ public function test_tag_input_with_no_tags_but_skips()
]);

$this->artisan('make:publication test-publication')
->expectsOutput('Warning: No tags for this publication type found in tags.json')
->expectsOutput(' Warning: No tags for this publication type found in tags.json')
->expectsConfirmation('Would you like to skip this field?', 'yes')
->doesntExpectOutput('Error: Unable to locate any tags for this publication type')
->assertExitCode(0);

$this->assertDatedPublicationExists();
$this->assertCreatedPublicationMatterEquals('tag: null');
$this->assertEquals(
<<<'MARKDOWN'
---
__createdAt: 2022-01-01T00:00:00+00:00
---

## Write something awesome.


MARKDOWN, $this->getDatedPublicationContents());
}

public function test_handleEmptyOptionsCollection_for_required_field()
Expand All @@ -367,11 +421,35 @@ public function test_handleEmptyOptionsCollection_for_required_field()
]);

$this->artisan('make:publication test-publication')
->doesntExpectOutput('Warning: No tags for this publication type found in tags.json')
->doesntExpectOutput(' Warning: No tags for this publication type found in tags.json')
->expectsOutput('Error: Unable to create publication: No tags for this publication type found in tags.json')
->assertExitCode(1);
}

public function test_with_custom_validation_rules()
{
$this->makeSchemaFile([
'canonicalField' => '__createdAt',
'fields' => [[
'type' => 'integer',
'name' => 'integer',
'rules' => ['max:10'],
],
],
]);

$this->artisan('make:publication test-publication')
->expectsQuestion('Enter data for field </>[<comment>integer</comment>]', 'string')
->expectsOutput('The integer must be a number.')
->expectsQuestion('Enter data for field </>[<comment>integer</comment>]', 15)
->expectsOutput('The integer must not be greater than 10.')
->expectsQuestion('Enter data for field </>[<comment>integer</comment>]', 5)
->assertExitCode(0);

$this->assertDatedPublicationExists();
$this->assertCreatedPublicationMatterEquals('integer: 5');
}

protected function makeSchemaFile(array $merge = []): void
{
file_put_contents(
Expand Down
2 changes: 1 addition & 1 deletion packages/framework/tests/Unit/InputStreamHandlerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public function testCanTerminateWithUnixEndings()

public function testFormatMessageHelperMethod()
{
$this->assertSame('Enter foo (end with an empty line)', InputStreamHandler::formatMessage('foo'));
$this->assertSame('<info>Enter values for field </>[<comment>foo</comment>] (end with an empty line)', InputStreamHandler::formatMessage('foo'));
}

protected function makeCommand(array $expected): TestCommand
Expand Down