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

Refactor publication commands and create actions #688

Merged
merged 156 commits into from
Nov 25, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
156 commits
Select commit Hold shift + click to select a range
15c3d05
Expect selection string in output
caendesilva Nov 24, 2022
ea15ae7
Refactor selection to be more readable
caendesilva Nov 24, 2022
6a07dde
Refactor to use built in ChoiceQuestion helper
caendesilva Nov 24, 2022
28bb8f7
Fail if pubType could not be found
caendesilva Nov 24, 2022
b169bf2
Replace failing return value with exceptions
caendesilva Nov 24, 2022
4c39936
Catch exceptions at end of handle method and return fail code there
caendesilva Nov 24, 2022
411445d
Import function
caendesilva Nov 24, 2022
656eca1
Add todo
caendesilva Nov 24, 2022
0062276
Extract method
caendesilva Nov 24, 2022
c09a80e
Put variable assignment in if block to find missing code coverage
caendesilva Nov 24, 2022
aef96ce
Apply fixes from StyleCI
StyleCIBot Nov 24, 2022
bb4cc36
Extract test helper
caendesilva Nov 24, 2022
50b1b81
Convert JSON test string to array
caendesilva Nov 24, 2022
8844f15
Apply fixes from StyleCI
StyleCIBot Nov 24, 2022
593626c
Remove redundant function call
caendesilva Nov 24, 2022
f1ae50f
Fix output expectation order
caendesilva Nov 24, 2022
9a45561
Display selection
caendesilva Nov 24, 2022
dd05d0e
Format output to match Laravel commands
caendesilva Nov 24, 2022
2bb7bd1
Freeze time to make testing easier
caendesilva Nov 24, 2022
4e6ce55
Don't display exception file location if it's caused by the command file
caendesilva Nov 24, 2022
b56d232
Test with no publication types
caendesilva Nov 24, 2022
aa58c7f
Test with file conflicts
caendesilva Nov 24, 2022
de234f6
Remove debug output and normalize message
caendesilva Nov 24, 2022
55bc811
Use FileConflictException instead of InvalidArgumentException
caendesilva Nov 24, 2022
107d157
Fix Copilot whoops (touch instead of mkdir)
caendesilva Nov 24, 2022
935a728
Specify custom exception message
caendesilva Nov 24, 2022
eace87c
Expect graceful exit message
caendesilva Nov 24, 2022
248a879
Assert conflicted file was not changed
caendesilva Nov 24, 2022
dd6ec39
Test conflict with overwrite
caendesilva Nov 24, 2022
c90c0c5
Unwrap redundant catch block
caendesilva Nov 24, 2022
87208bc
Add todo
caendesilva Nov 24, 2022
a6548cc
Apply fixes from StyleCI
StyleCIBot Nov 24, 2022
b8b7abd
Add strict types declaration
caendesilva Nov 24, 2022
e974520
Rename interface CreateActionInterface to CreateActionContract
caendesilva Nov 24, 2022
3f4d776
Rename namespace Actions\Interfaces to Actions\Contracts
caendesilva Nov 24, 2022
5986acf
Create abstract class CreateAction implements CreateActionContract
caendesilva Nov 24, 2022
b287c97
Create CreateActionTest.php
caendesilva Nov 24, 2022
6b47aef
Apply fixes from StyleCI
StyleCIBot Nov 24, 2022
6e2cd7f
Contracted method throws FileConflictException
caendesilva Nov 24, 2022
4db6643
Add inheritDoc tag
caendesilva Nov 24, 2022
f62807b
Add helper methods to contract
caendesilva Nov 24, 2022
90570f2
Add control properties
caendesilva Nov 24, 2022
0a7ad82
Implement getOutputPath and force methods
caendesilva Nov 24, 2022
8229c29
Update conflict prediction helpers
caendesilva Nov 24, 2022
43072b0
Add and update PHPDocs
caendesilva Nov 24, 2022
67c7b83
Add output path setter
caendesilva Nov 24, 2022
42b0d92
Make setters fluent
caendesilva Nov 24, 2022
640c346
Add abstract protected function handleCreate
caendesilva Nov 24, 2022
acf02bb
Implement create() method
caendesilva Nov 24, 2022
f5cdc0f
Reorder methods
caendesilva Nov 24, 2022
0352622
Apply fixes from StyleCI
StyleCIBot Nov 24, 2022
a7a04ca
Use helper method instead of duplicate code
caendesilva Nov 24, 2022
eeee41f
Filesystem calls should use absolute path
caendesilva Nov 24, 2022
187375c
Add helper to put contents at absolute path
caendesilva Nov 24, 2022
1fa759f
Add CreateActionTestClass
caendesilva Nov 24, 2022
e94247d
Extract and contract getAbsoluteOutputPath method
caendesilva Nov 24, 2022
20db48d
Test create method
caendesilva Nov 24, 2022
f387363
Use helper instead of repeated code
caendesilva Nov 24, 2022
e3586ac
Fully test the class
caendesilva Nov 24, 2022
1955d2a
Apply fixes from StyleCI
StyleCIBot Nov 24, 2022
7a0cdf6
Use InteractsWithDirectories and ensure parent directory exists for s…
caendesilva Nov 24, 2022
89e98fc
Apply fixes from StyleCI
StyleCIBot Nov 24, 2022
6de1177
Refactor CreatesNewPublicationFile to use CreateAction
caendesilva Nov 24, 2022
b16e5da
Refactor CreatesNewPublicationType to use CreateAction
caendesilva Nov 24, 2022
a07652e
Add fixme tag
caendesilva Nov 24, 2022
772739e
Mark experimental classes as internal
caendesilva Nov 24, 2022
dcdae32
Rename internal helper
caendesilva Nov 24, 2022
f1c8f25
Add helper to format string for storage
caendesilva Nov 24, 2022
2dcd4d2
Apply fixes from StyleCI
StyleCIBot Nov 24, 2022
ce5c8fe
Merge pull request #689 from hydephp/refactor-CreateActionInterface
caendesilva Nov 24, 2022
0724249
Use absolute path when interacting with filesystem
caendesilva Nov 24, 2022
111a51b
Create directories using absolute paths
caendesilva Nov 24, 2022
8e8fbcb
Revert "Create directories using absolute paths"
caendesilva Nov 24, 2022
33c473a
Clean up code
caendesilva Nov 24, 2022
798eb3e
Apply fixes from StyleCI
StyleCIBot Nov 24, 2022
1b201f4
Code style
caendesilva Nov 24, 2022
4fb4818
Use Safe function to ensure function call worked
caendesilva Nov 24, 2022
d4aa594
Use absolute path
caendesilva Nov 24, 2022
e8f5a74
Get result from file
caendesilva Nov 24, 2022
ca0ab06
Remove result cache and getter (just load the file instead)
caendesilva Nov 24, 2022
9f5c80a
Remove unused result cache and getter
caendesilva Nov 24, 2022
341051b
Update default output text to match make:post command
caendesilva Nov 24, 2022
0bcfdb4
Allow empty directories to be used
caendesilva Nov 24, 2022
4fa540b
Add PHPDoc links between related commands and actions
caendesilva Nov 24, 2022
dc5dcbb
Add newline before output
caendesilva Nov 24, 2022
18aebed
Merge branch 'master' into refactor-publication-commands
caendesilva Nov 24, 2022
32e63ef
Merge branch 'master' into refactor-publication-commands
caendesilva Nov 24, 2022
4fb8491
Merge branch 'master' into refactor-publication-commands
caendesilva Nov 24, 2022
8c66fa9
Merge branch 'publications-feature' into refactor-publication-commands
caendesilva Nov 24, 2022
2fd65af
Only print warnings if there are any
caendesilva Nov 24, 2022
7ba6fb6
Exit early to prevent duplicate exit message
caendesilva Nov 25, 2022
59bd35f
Use built-in confirm helper
caendesilva Nov 25, 2022
542adb1
Inline variable
caendesilva Nov 25, 2022
6e75d66
Normalize constructor call for clearer code without affecting semantics
caendesilva Nov 25, 2022
01d521c
Extract helper method
caendesilva Nov 25, 2022
91dfaab
Move extracted helper method to parent class
caendesilva Nov 25, 2022
e351ec0
Use debug_backtrace to get the previous file instead of using magic c…
caendesilva Nov 25, 2022
03a9575
Document code
caendesilva Nov 25, 2022
ff20742
Make helper public to match base class convention
caendesilva Nov 25, 2022
da2d4fc
Apply fixes from StyleCI
StyleCIBot Nov 25, 2022
c3ea703
Generate PHPDoc
caendesilva Nov 25, 2022
bc035d9
Add method description
caendesilva Nov 25, 2022
4fc3bac
Remove redundant type annotations inferred by strong types
caendesilva Nov 25, 2022
6d64de1
Document throws tag
caendesilva Nov 25, 2022
0cf2165
Pass the default parameter instead of null when retrying
caendesilva Nov 25, 2022
a39353a
Test handleException helper
caendesilva Nov 25, 2022
b0fff10
Add helper to specify the calling file
caendesilva Nov 25, 2022
3e79790
Refactor handler to feel more intuitive
caendesilva Nov 25, 2022
b28e03e
Add extra test case
caendesilva Nov 25, 2022
16a50cb
Move up comment and invert if/else block
caendesilva Nov 25, 2022
4858f6b
Revert "Refactor handler to feel more intuitive"
caendesilva Nov 25, 2022
42ea181
Apply fixes from StyleCI
StyleCIBot Nov 25, 2022
9724642
Test command with publication type passed as argument
caendesilva Nov 25, 2022
494dde0
Extract assertion helper
caendesilva Nov 25, 2022
42f3fc6
Convert string literal to NOWDOC
caendesilva Nov 25, 2022
7d6ce31
Display the invalid selection string
caendesilva Nov 25, 2022
16d0cb0
Test with invalid publication passed as argument
caendesilva Nov 25, 2022
17a9106
Inline local variable
caendesilva Nov 25, 2022
c9be6c3
Refactor control flow without changing semantics
caendesilva Nov 25, 2022
6ac8f55
Simplify 'if' with ternary operator
caendesilva Nov 25, 2022
3956900
Replace ternary operator with null coalesce assignment
caendesilva Nov 25, 2022
cafb16b
Formatting
caendesilva Nov 25, 2022
148f804
Apply fixes from StyleCI
StyleCIBot Nov 25, 2022
533c7b8
Add type annotations
caendesilva Nov 25, 2022
def6d71
Remove type annotation inferred from class
caendesilva Nov 25, 2022
28dbfa8
Add static fromArray constructor
caendesilva Nov 25, 2022
0ecf00f
Convert array to PublicationFieldType when interacting with it
caendesilva Nov 25, 2022
ddb7eeb
Add type annotation
caendesilva Nov 25, 2022
6bb5e27
Extract method
caendesilva Nov 25, 2022
d1def62
Shift type annotation to return value
caendesilva Nov 25, 2022
f601603
Put output first in method
caendesilva Nov 25, 2022
d1a85d9
Use FQCN in PHPDocs
caendesilva Nov 25, 2022
273c80f
Refactor foreach loop to collection mapper
caendesilva Nov 25, 2022
194acba
Generate PHPDoc with generics
caendesilva Nov 25, 2022
a0ca5c3
Extract default rules constant
caendesilva Nov 25, 2022
4c901e8
Extract method
caendesilva Nov 25, 2022
02f6c9c
Inline helper method
caendesilva Nov 25, 2022
04d2876
Inline unnecessary local variable
caendesilva Nov 25, 2022
c365e38
Apply fixes from StyleCI
StyleCIBot Nov 25, 2022
63a08bd
Add user exit code constant
caendesilva Nov 25, 2022
f2082d3
Return user exited code 130
caendesilva Nov 25, 2022
c652211
Use file conflict prediction helpers instead of complex nested try-ca…
caendesilva Nov 25, 2022
b34749f
Add experimental helper for exiting with message
caendesilva Nov 25, 2022
e9cc748
Apply fixes from StyleCI
StyleCIBot Nov 25, 2022
395ae69
Add --force option
caendesilva Nov 25, 2022
6f7a6d2
Ensure type is bool
caendesilva Nov 25, 2022
75da360
Extract method
caendesilva Nov 25, 2022
e5a2373
Rename method fileConflicts to hasFileConflict
caendesilva Nov 25, 2022
a0931ad
Revert "Add experimental helper for exiting with message"
caendesilva Nov 25, 2022
081f32e
Add feature to base command class to automatically handle exceptions
caendesilva Nov 25, 2022
d0ba8d3
Return exit code in test class
caendesilva Nov 25, 2022
7b8343a
Simplify handler by statically matching filename
caendesilva Nov 25, 2022
fede192
Expect mocked filepath
caendesilva Nov 25, 2022
1c105cf
Refactor exception handler to allow path information to be set
caendesilva Nov 25, 2022
228742f
Convert string interpolation to a 'sprintf()' call
caendesilva Nov 25, 2022
2db244c
Apply fixes from StyleCI
StyleCIBot Nov 25, 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
6 changes: 4 additions & 2 deletions monorepo/HydeStan/HydeStan.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,10 @@ public function __destruct()
number_format(count($this->files)))
);

// Forward warnings to GitHub Actions
$this->console->line(sprintf("\n%s", implode("\n", self::$warnings)));
if (count(self::$warnings) > 0) {
// Forward warnings to GitHub Actions
$this->console->line(sprintf("\n%s", implode("\n", self::$warnings)));
}
}

public function run(): void
Expand Down
135 changes: 72 additions & 63 deletions packages/framework/src/Console/Commands/MakePublicationCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@

namespace Hyde\Console\Commands;

use Exception;
use Hyde\Console\Commands\Interfaces\CommandHandleInterface;
use Hyde\Console\Concerns\ValidatingCommand;
use Hyde\Framework\Actions\CreatesNewPublicationFile;
use Hyde\Framework\Features\Publications\Models\PublicationFieldType;
use Hyde\Framework\Features\Publications\Models\PublicationType;
use Hyde\Framework\Features\Publications\PublicationService;
use Illuminate\Support\Str;
use InvalidArgumentException;
Expand All @@ -17,82 +18,47 @@
/**
* Hyde Command to create a new publication for a given publication type.
*
* @see \Hyde\Framework\Actions\CreatesNewPublicationFile
* @see \Hyde\Framework\Testing\Feature\Commands\MakePublicationCommandTest
*/
class MakePublicationCommand extends ValidatingCommand implements CommandHandleInterface
{
/** @var string */
protected $signature = 'make:publication
{publicationType? : The name of the PublicationType to create a publication for}';
{publicationType? : The name of the PublicationType to create a publication for}
{--force : Should the generated file overwrite existing publications with the same filename?}';

/** @var string */
protected $description = 'Create a new publication item';

public function handle(): int
public function safeHandle(): int
{
$this->title('Creating a new Publication!');

$pubTypes = PublicationService::getPublicationTypes();
if ($pubTypes->isEmpty()) {
$this->output->error('Unable to locate any publication types. Did you create any?');

return Command::FAILURE;
}

$pubType = $this->argument('publicationType');
if (! $pubType) {
$this->output->writeln('<bg=magenta;fg=white>Now please choose the Publication Type to create an item for:</>');
$offset = 0;
foreach ($pubTypes as $pubType) {
$offset++;
$this->line(" $offset: $pubType->name");
}
$selected = (int) $this->askWithValidation('selected', "Publication type (1-$offset)", ['required', 'integer', "between:1,$offset"]);
$pubType = $pubTypes->{$pubTypes->keys()[$selected - 1]};
}

$mediaFiles = PublicationService::getMediaForPubType($pubType);
$fieldData = Collection::create();
$this->output->writeln('<bg=magenta;fg=white>Now please enter the field data:</>');
foreach ($pubType->fields as $field) {
$fieldData->{$field['name']} = $this->captureFieldInput((object) $field, $mediaFiles);
}
$pubType = $this->getPubTypeSelection($this->getPublicationTypes());
$fieldData = $this->collectFieldData($pubType);

try {
$creator = new CreatesNewPublicationFile($pubType, $fieldData, output: $this->output);
$creator->create();
} catch (InvalidArgumentException $exception) { // FIXME: provide a properly typed exception
$msg = $exception->getMessage();
// Useful for debugging
//$this->output->writeln("xxx " . $exception->getTraceAsString());
$this->output->writeln("<bg=red;fg=white>$msg</>");
$overwrite = $this->askWithValidation(
'overwrite',
'Do you wish to overwrite the existing file (y/n)',
['required', 'string', 'in:y,n'],
'n'
);
if (strtolower($overwrite) == 'y') {
$creator = new CreatesNewPublicationFile($pubType, $fieldData, true, $this->output);
$creator->create();
$creator = new CreatesNewPublicationFile($pubType, $fieldData, $this->hasForceOption(), $this->output);
if ($creator->hasFileConflict()) {
$this->error('Error: A publication already exists with the same canonical field value');
if ($this->confirm('Do you wish to overwrite the existing file?')) {
$creator->force();
} else {
$this->output->writeln('<bg=magenta;fg=white>Exiting without overwriting existing publication file!</>');
}
} catch (Exception $exception) {
$this->error("Error: {$exception->getMessage()} at {$exception->getFile()}:{$exception->getLine()}");

return Command::FAILURE;
return ValidatingCommand::USER_EXIT;
}
}

$creator->create();

$this->info('Publication created successfully!');

return Command::SUCCESS;
}

protected function captureFieldInput(object $field, Collection $mediaFiles): string|array
protected function captureFieldInput(PublicationFieldType $field, Collection $mediaFiles): string|array
{
$rulesPerType = $this->getValidationRulesPerType();

if ($field->type === 'text') {
$lines = [];
$this->output->writeln($field->name." (end with a line containing only '<<<')");
Expand Down Expand Up @@ -135,7 +101,7 @@ protected function captureFieldInput(object $field, Collection $mediaFiles): str
}

// Fields which are not of type array, text or image
$fieldRules = $rulesPerType->{$field->type};
$fieldRules = Collection::create(PublicationFieldType::DEFAULT_RULES)->{$field->type};
if ($fieldRules->contains('between')) {
$fieldRules->forget($fieldRules->search('between'));
if ($field->min && $field->max) {
Expand All @@ -156,16 +122,59 @@ protected function captureFieldInput(object $field, Collection $mediaFiles): str
return $this->askWithValidation($field->name, $field->name, $fieldRules);
}

protected function getValidationRulesPerType(): Collection
/**
* @param \Rgasch\Collection\Collection<string, \Hyde\Framework\Features\Publications\Models\PublicationType> $pubTypes
* @return \Hyde\Framework\Features\Publications\Models\PublicationType
*/
protected function getPubTypeSelection(Collection $pubTypes): PublicationType
{
$pubTypeSelection = $this->argument('publicationType') ?? $pubTypes->keys()->get(
(int) $this->choice('Which publication type would you like to create a publication item for?',
$pubTypes->keys()->toArray()
)
);

if ($pubTypes->has($pubTypeSelection)) {
$this->line("<info>Creating a new publication of type</info> [<comment>$pubTypeSelection</comment>]");

return $pubTypes->get($pubTypeSelection);
}

throw new InvalidArgumentException("Unable to locate publication type [$pubTypeSelection]");
}

/**
* @param \Hyde\Framework\Features\Publications\Models\PublicationType $pubType
* @return \Rgasch\Collection\Collection<string, string|array>
*/
protected function collectFieldData(PublicationType $pubType): Collection
{
$this->output->writeln("\n<bg=magenta;fg=white>Now please enter the field data:</>");

$mediaFiles = PublicationService::getMediaForPubType($pubType);

return Collection::make($pubType->fields)->mapWithKeys(function ($field) use ($mediaFiles) {
return [$field['name'] => $this->captureFieldInput(PublicationFieldType::fromArray($field), $mediaFiles)];
});
}

/**
* @return \Rgasch\Collection\Collection<string, PublicationType>
*
* @throws \InvalidArgumentException
*/
protected function getPublicationTypes(): Collection
{
$pubTypes = PublicationService::getPublicationTypes();
if ($pubTypes->isEmpty()) {
throw new InvalidArgumentException('Unable to locate any publication types. Did you create any?');
}

return $pubTypes;
}

protected function hasForceOption(): bool
{
return Collection::create([
'string' => ['required', 'string', 'between'],
'boolean' => ['required', 'boolean'],
'integer' => ['required', 'integer', 'between'],
'float' => ['required', 'numeric', 'between'],
'datetime' => ['required', 'datetime', 'between'],
'url' => ['required', 'url'],
'text' => ['required', 'string', 'between'],
]);
return (bool) $this->option('force');
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
/**
* Hyde Command to create a new publication type.
*
* @see \Hyde\Framework\Actions\CreatesNewPublicationType
* @see \Hyde\Framework\Testing\Feature\Commands\MakePublicationTypeCommandTest
*/
class MakePublicationTypeCommand extends ValidatingCommand implements CommandHandleInterface
Expand All @@ -35,7 +36,7 @@ public function handle(): int
if (! $title) {
$title = trim($this->askWithValidation('name', 'Publication type name', ['required', 'string']));
$dirname = PublicationService::formatNameForStorage($title);
if (file_exists($dirname)) {
if (file_exists($dirname) && is_dir($dirname) && count(scandir($dirname)) > 2) {
throw new InvalidArgumentException("Storage path [$dirname] already exists");
}
}
Expand Down
54 changes: 47 additions & 7 deletions packages/framework/src/Console/Concerns/ValidatingCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@

use function array_keys;
use function array_values;
use Exception;
use Illuminate\Contracts\Support\Arrayable;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Str;
use LaravelZero\Framework\Commands\Command;
use RuntimeException;
use function str_ends_with;
use function str_replace;
use function ucfirst;

Expand All @@ -21,20 +23,40 @@
*/
class ValidatingCommand extends Command
{
public const USER_EXIT = 130;

/** @var int How many times can the validation loop run? Guards against infinite loops. */
protected final const MAX_RETRIES = 30;

/**
* @return int The exit code.
*/
public function handle(): int
{
try {
return $this->safeHandle();
} catch (Exception $exception) {
return $this->handleException($exception);
}
}

/**
* This method can be overridden by child classes to provide automatic exception handling.
* Existing code can be converted simply by renaming the handle() method to safeHandle().
*
* @return int The exit code.
*/
protected function safeHandle(): int
{
return Command::SUCCESS;
}

/**
* Ask for a CLI input value until we pass validation rules.
*
* @param string $name
* @param string $question
* @param \Illuminate\Contracts\Support\Arrayable|array $rules
* @param mixed|null $default
* @param int $retryCount How many times has the question been asked?
* @return mixed
*
* @throws RuntimeException
* @throws RuntimeException If the validation fails after MAX_RETRIES attempts.
*/
public function askWithValidation(
string $name,
Expand Down Expand Up @@ -65,7 +87,25 @@ public function askWithValidation(
throw new RuntimeException(sprintf("Too many validation errors trying to validate '$name' with rules: [%s]", implode(', ', $rules)));
}

return $this->askWithValidation($name, $question, $rules, null, $retryCount);
return $this->askWithValidation($name, $question, $rules, $default, $retryCount);
}

/**
* Handle an exception that occurred during command execution.
*
* @param string|null $file The file where the exception occurred. Leave null to auto-detect.
* @return int The exit code
*/
public function handleException(Exception $exception, ?string $file = null, ?int $line = null): int
{
// If the exception was thrown from the same file as a command, then we don't need to show which file it was thrown from.
if (str_ends_with($file ?? $exception->getFile(), 'Command.php')) {
$this->error("Error: {$exception->getMessage()}");
} else {
$this->error(sprintf('Error: %s at ', $exception->getMessage()).sprintf('%s:%s', $file ?? $exception->getFile(), $line ?? $exception->getLine()));
}

return Command::FAILURE;
}

protected function translate($name, string $error): string
Expand Down
88 changes: 88 additions & 0 deletions packages/framework/src/Framework/Actions/Concerns/CreateAction.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
<?php

declare(strict_types=1);

namespace Hyde\Framework\Actions\Concerns;

use function file_exists;
use Hyde\Framework\Actions\Contracts\CreateActionContract;
use Hyde\Framework\Concerns\InteractsWithDirectories;
use Hyde\Framework\Exceptions\FileConflictException;
use Hyde\Hyde;
use Illuminate\Support\Str;

/**
* @see \Hyde\Framework\Testing\Feature\CreateActionTest
*
* @internal This class is experimental and is not meant to be used outside the Hyde framework.
*/
abstract class CreateAction implements CreateActionContract
{
use InteractsWithDirectories;

protected string $outputPath;
protected bool $force = false;

abstract protected function handleCreate(): void;

/** @inheritDoc */
public function create(): void
{
if ($this->hasFileConflict()) {
throw new FileConflictException($this->outputPath);
}

$this->handleCreate();
}

/** @inheritDoc */
public function force(bool $force = true): static
{
$this->force = $force;

return $this;
}

/** @inheritDoc */
public function setOutputPath(string $outputPath): static
{
$this->outputPath = $outputPath;

return $this;
}

/** @inheritDoc */
public function getOutputPath(): string
{
return $this->outputPath;
}

/** @inheritDoc */
public function getAbsoluteOutputPath(): string
{
return Hyde::path($this->getOutputPath());
}

/** @inheritDoc */
public function fileExists(): bool
{
return file_exists($this->getAbsoluteOutputPath());
}

/** @inheritDoc */
public function hasFileConflict(): bool
{
return $this->fileExists() && ! $this->force;
}

protected function save(string $contents): void
{
$this->needsParentDirectory($this->getAbsoluteOutputPath());
\Safe\file_put_contents($this->getAbsoluteOutputPath(), $contents);
}

protected function formatStringForStorage(string $string): string
{
return Str::slug($string);
}
}
Loading