Skip to content

Commit

Permalink
Refactor
Browse files Browse the repository at this point in the history
  • Loading branch information
aerni committed Mar 30, 2024
1 parent 1a0a104 commit d1e5a59
Show file tree
Hide file tree
Showing 13 changed files with 204 additions and 299 deletions.
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
],
"require": {
"php": "^8.2",
"illuminate/support": "^11.0"
"illuminate/support": "^11.0",
"laravel/prompts": "^0.1.17"
},
"require-dev": {
"nunomaduro/collision": "^8.1",
Expand Down
6 changes: 1 addition & 5 deletions config/sync.php
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,7 @@
*/

'options' => [
// '--archive',
// '--itemize-changes',
// '--verbose',
// '--human-readable',
// '--progress'
'--archive',
],

];
101 changes: 0 additions & 101 deletions src/CommandGenerator.php

This file was deleted.

134 changes: 111 additions & 23 deletions src/Commands/BaseCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,63 +2,151 @@

namespace Aerni\Sync\Commands;

use Facades\Aerni\Sync\CommandGenerator;
use Facades\Aerni\Sync\Config;
use Aerni\Sync\SyncCommand;
use Illuminate\Support\Arr;
use Aerni\Sync\PathGenerator;
use Illuminate\Console\Command;
use Illuminate\Validation\Rule;
use Illuminate\Support\Collection;
use function Laravel\Prompts\select;
use Illuminate\Support\Facades\Validator;
use Symfony\Component\Console\Output\OutputInterface;
use Illuminate\Contracts\Console\PromptsForMissingInput;

class BaseCommand extends Command
class BaseCommand extends Command implements PromptsForMissingInput
{
public function __construct()
{
$baseSignature = "
{operation : Choose if you want to 'push' or 'pull'}
$baseSignature = '
{operation : Choose if you want to push or pull}
{remote : The remote you want to sync with}
{recipe : The recipe defining the paths to sync}
{--O|option=* : An rsync option to use}
{--D|dry : Perform a dry run of the sync}
";
';

$this->signature .= $baseSignature;

parent::__construct();
}

protected function commandGenerator(): \Aerni\Sync\CommandGenerator
protected function promptForMissingArgumentsUsing(): array
{
return CommandGenerator::operation($this->operation())
->remote($this->remote())
->recipe($this->recipe())
->options($this->rsyncOptions());
return [
'operation' => fn () => select(
label: 'Choose if you want to push or pull',
options: ['push', 'pull'],
),
'remote' => fn () => select(
label: 'Choose the remote you want to sync with',
options: array_keys($this->remotes()),
),
'recipe' => fn () => select(
label: 'Choose the recipe defining the paths to sync',
options: array_keys($this->recipes()),
),
];
}

protected function validate(): void
{
Validator::validate($this->arguments(), [
'operation' => 'required|in:push,pull',
'remote' => ['required', Rule::in(array_keys($this->remotes()))],
'recipe' => ['required', Rule::in(array_keys($this->recipes()))],
], [
'operation.in' => "The :attribute [:input] does not exists. Valid values are [push] or [pull].",
'remote.in' => "The :attribute [:input] does not exists. Please choose a valid remote.",
'recipe.in' => "The :attribute [:input] does not exists. Please choose a valid recipe.",
]);

if ($this->localPathEqualsRemotePath()) {
throw new \RuntimeException("The origin and target path are one and the same. You can't sync a path with itself.");
}

if ($this->remoteIsReadOnly() && $this->operation() === 'push') {
throw new \RuntimeException("You can't push to the selected target as it is configured to be read-only.");
}
}

protected function localPathEqualsRemotePath(): bool
{
return PathGenerator::localPath($this->recipe()[0])
=== PathGenerator::remotePath($this->remote(), $this->recipe()[0]);
}

protected function remoteIsReadOnly(): bool
{
return Arr::get($this->remote(), 'read_only', false);
}

protected function commands(): Collection
{
return collect($this->recipe())
->map(fn ($path) => new SyncCommand(
path: $path,
operation: $this->operation(),
remote: $this->remote(),
options: $this->rsyncOptions(),
));
}

protected function operation(): string
{
return Config::operation($this->argument('operation'));
return $this->argument('operation');
}

protected function remote(): array
{
return Config::remote($this->argument('remote'));
return Arr::get($this->remotes(), $this->argument('remote'));
}

protected function recipe(): array
{
return Config::recipe($this->argument('recipe'));
return Arr::get($this->recipes(), $this->argument('recipe'));
}

protected function rsyncOptions(): array
protected function remotes(): array
{
$options = Config::options($this->option('option'));
$remotes = config('sync.remotes');

return collect($options)
->push($this->dry())
->filter()
->unique()
->toArray();
if (empty($remotes)) {
throw new \RuntimeException('You need to define at least one remote in your config file.');
}

return $remotes;
}

protected function recipes(): array
{
$recipes = config('sync.recipes');

if (empty($recipes)) {
throw new \RuntimeException('You need to define at least one recipe in your config file.');
}

return $recipes;
}

protected function dry(): string
protected function rsyncOptions(): string
{
return $this->option('dry') ? '--dry-run' : '';
$options = $this->option('option');

if (empty($options)) {
$options = config('sync.options');
}

return collect($options)
->when(
$this->option('dry'),
fn ($collection) => $collection->merge(['--dry-run', '--human-readable', '--progress', '--stats', '--verbose'])
)
->when(
$this->output->getVerbosity() >= OutputInterface::VERBOSITY_VERBOSE,
fn ($collection) => $collection->merge(['--human-readable', '--progress', '--stats', '--verbose'])
)
->filter()
->unique()
->implode(' ');
}
}
37 changes: 21 additions & 16 deletions src/Commands/Sync.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@

namespace Aerni\Sync\Commands;

use Facades\Aerni\Sync\SyncProcessor;
use Illuminate\Support\Arr;
use function Laravel\Prompts\confirm;
use Illuminate\Support\Facades\Process;
use Symfony\Component\Console\Output\OutputInterface;

class Sync extends BaseCommand
{
Expand All @@ -26,25 +27,29 @@ class Sync extends BaseCommand
*/
public function handle(): void
{
if ($this->operation() === 'push' && Arr::get($this->remote(), 'read_only') === true) {
$this->error("You can't push to the selected target as it is configured to be read only.");
$this->validate();

/* Only show the confirmation if we're not performing a dry run */
if (! $this->option('dry') && ! confirm($this->confirmText())) {
return;
}

if (! $this->confirm($this->confirmText(), true)) {
return;
}
$this->option('dry')
? $this->info('Starting a dry run ...')
: $this->info('Syncing files ...');

$commands = $this->commandGenerator()->run();
$this->commands()->each(function ($command) {
Process::forever()->run($command, function (string $type, string $output) {
/* Only show the output if we're performing a dry run or the verbosity is set to verbose */
if ($this->option('dry') || $this->output->getVerbosity() >= OutputInterface::VERBOSITY_VERBOSE) {
echo $output;
}
});
});

$sync = SyncProcessor::commands($commands)
->artisanCommand($this)
->run();

if ($sync->successful()) {
$this->info('The sync was successful');
}
$this->option('dry')
? $this->info("The dry run of the <comment>{$this->argument('recipe')}</comment> recipe was successfull.")
: $this->info("The sync of the <comment>{$this->argument('recipe')}</comment> recipe was successfull.");
}

protected function confirmText(): string
Expand All @@ -54,6 +59,6 @@ protected function confirmText(): string
$remote = $this->argument('remote');
$preposition = $operation === 'pull' ? 'from' : 'to';

return "Please confirm that you want to <comment>$operation</comment> the <comment>$recipe</comment> $preposition <comment>$remote</comment>";
return "You are about to $operation the $recipe $preposition $remote. Are you sure?";
}
}
7 changes: 3 additions & 4 deletions src/Commands/SyncCommands.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,8 @@ class SyncCommands extends BaseCommand
*/
public function handle(): void
{
$this->commandGenerator()->commandsString()->each(function ($command) {
$this->info($command);
$this->newLine();
});
$this->validate();

$this->commands()->each(fn ($command) => $this->info($command));
}
}
10 changes: 7 additions & 3 deletions src/Commands/SyncList.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

namespace Aerni\Sync\Commands;

use function Laravel\Prompts\table;

class SyncList extends BaseCommand
{
/**
Expand All @@ -23,9 +25,11 @@ class SyncList extends BaseCommand
*/
public function handle(): void
{
$headers = ['Origin', 'Target', 'Options', 'Port'];
$commands = $this->commandGenerator()->commandsArray();
$this->validate();

$this->table($headers, $commands);
table(
['Origin', 'Target', 'Options', 'Port'],
$this->commands()->map->toArray()
);
}
}
Loading

0 comments on commit d1e5a59

Please sign in to comment.