mirror of
https://github.com/BillyOutlast/UNIT3D.git
synced 2026-02-04 03:01:20 +01:00
280 lines
7.7 KiB
PHP
280 lines
7.7 KiB
PHP
<?php
|
||
|
||
declare(strict_types=1);
|
||
|
||
/**
|
||
* NOTICE OF LICENSE.
|
||
*
|
||
* UNIT3D Community Edition is open-sourced software licensed under the GNU Affero General Public License v3.0
|
||
* The details is bundled with this project in the file LICENSE.txt.
|
||
*
|
||
* @project UNIT3D Community Edition
|
||
*
|
||
* @author HDVinnie <hdinnovations@protonmail.com>
|
||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html/ GNU Affero General Public License v3.0
|
||
*/
|
||
|
||
namespace App\Console;
|
||
|
||
use Symfony\Component\Console\Helper\ProgressBar;
|
||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||
use Symfony\Component\Process\Exception\ProcessTimedOutException;
|
||
use Symfony\Component\Process\Process;
|
||
use RuntimeException;
|
||
|
||
trait ConsoleTools
|
||
{
|
||
protected SymfonyStyle $io;
|
||
|
||
/**
|
||
* Log a message to a file.
|
||
*/
|
||
protected function log(string $message, ?string $logFile = null): void
|
||
{
|
||
$logFile ??= $this->logFile ?? storage_path('logs/console-'.now()->format('Y-m-d').'.log');
|
||
$timestamp = now()->toDateTimeString();
|
||
$logMessage = "[{$timestamp}] {$message}".PHP_EOL;
|
||
|
||
if (!file_exists(\dirname($logFile)) && !mkdir(
|
||
$concurrentDirectory = \dirname($logFile),
|
||
0755,
|
||
true
|
||
) && !is_dir($concurrentDirectory)) {
|
||
throw new RuntimeException(\sprintf('Directory "%s" was not created', $concurrentDirectory));
|
||
}
|
||
|
||
file_put_contents($logFile, $logMessage, FILE_APPEND);
|
||
}
|
||
|
||
/**
|
||
* Display a section header in a quote-like format.
|
||
*/
|
||
public function header(string $text): void
|
||
{
|
||
$length = mb_strlen($text) + 4; // Add padding
|
||
$border = str_repeat('━', $length);
|
||
|
||
$this->io->newLine();
|
||
$this->io->writeln("<fg=blue>┏{$border}┓</>");
|
||
$this->io->writeln("<fg=blue>┃</><fg=white> {$text} </><fg=blue>┃</>");
|
||
$this->io->writeln("<fg=blue>┗{$border}┛</>");
|
||
$this->io->newLine();
|
||
|
||
$this->log($text);
|
||
}
|
||
|
||
/**
|
||
* Display a success message.
|
||
*/
|
||
public function success(string $message): void
|
||
{
|
||
$this->io->writeln("<fg=green>✓ {$message}</>");
|
||
}
|
||
|
||
/**
|
||
* Display an error message.
|
||
*
|
||
* @param string|array<string> $string
|
||
* @param int|string|null $verbosity
|
||
*/
|
||
public function error($string, $verbosity = null): void
|
||
{
|
||
if (\is_array($string)) {
|
||
foreach ($string as $message) {
|
||
$this->io->writeln("<fg=red>✗ {$message}</>");
|
||
}
|
||
} else {
|
||
$this->io->writeln("<fg=red>✗ {$string}</>");
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Display an info message.
|
||
*
|
||
* @param string|array<string> $string
|
||
* @param int|string|null $verbosity
|
||
*/
|
||
public function info($string, $verbosity = null): void
|
||
{
|
||
if (\is_array($string)) {
|
||
foreach ($string as $message) {
|
||
$this->io->writeln("<fg=cyan>ℹ {$message}</>");
|
||
}
|
||
} else {
|
||
$this->io->writeln("<fg=cyan>ℹ {$string}</>");
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Display a warning message.
|
||
*/
|
||
public function warning(string $message): void
|
||
{
|
||
$this->io->writeln("<fg=yellow>⚠ {$message}</>");
|
||
}
|
||
|
||
/**
|
||
* Display a note message.
|
||
*/
|
||
public function note(string $message): void
|
||
{
|
||
$this->io->writeln("<fg=magenta>• {$message}</>");
|
||
}
|
||
|
||
/**
|
||
* Display a command being executed.
|
||
*/
|
||
protected function command(string $command): void
|
||
{
|
||
$this->io->writeln("<fg=blue>$ <fg=yellow>{$command}</>");
|
||
}
|
||
|
||
/**
|
||
* Execute a shell command with a progress bar.
|
||
*/
|
||
protected function execCommand(string $command, bool $silent = false): Process
|
||
{
|
||
if (!$silent) {
|
||
$this->io->newLine();
|
||
$this->command($command);
|
||
$progressBar = $this->createProgressBar();
|
||
}
|
||
|
||
$process = Process::fromShellCommandline($command);
|
||
$process->setTimeout(3600);
|
||
$process->start();
|
||
|
||
while ($process->isRunning()) {
|
||
try {
|
||
$process->checkTimeout();
|
||
} catch (ProcessTimedOutException) {
|
||
$this->error("Command timed out after 1 hour: '{$command}'");
|
||
}
|
||
|
||
if (!$silent) {
|
||
$progressBar->advance();
|
||
}
|
||
|
||
usleep(200000);
|
||
}
|
||
|
||
if (!$silent) {
|
||
$progressBar->finish();
|
||
$this->io->newLine();
|
||
|
||
if ($process->isSuccessful()) {
|
||
$this->success("Command completed successfully!");
|
||
} else {
|
||
$this->error("Command failed: ".$process->getErrorOutput());
|
||
}
|
||
}
|
||
|
||
$process->stop();
|
||
|
||
return $process;
|
||
}
|
||
|
||
/**
|
||
* Execute multiple shell commands.
|
||
*
|
||
* @param array<string> $commands
|
||
*/
|
||
protected function execCommands(array $commands, bool $silent = false): void
|
||
{
|
||
foreach ($commands as $command) {
|
||
$process = $this->execCommand($command, $silent);
|
||
|
||
if (!$silent && $process->getOutput() && trim($process->getOutput()) !== '') {
|
||
$this->io->writeln("<fg=gray>".trim($process->getOutput())."</>");
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Create a stylized progress bar.
|
||
*/
|
||
protected function createProgressBar(): ProgressBar
|
||
{
|
||
$progressBar = $this->io->createProgressBar();
|
||
$progressBar->setBarCharacter('<fg=cyan>▶</>');
|
||
$progressBar->setEmptyBarCharacter('<fg=gray>▷</>');
|
||
$progressBar->setProgressCharacter('<fg=green>▶</>');
|
||
$progressBar->setFormat(' %bar% <fg=cyan>%percent:3s%%</> %elapsed:6s%/%estimated:-6s% ');
|
||
$progressBar->start();
|
||
|
||
return $progressBar;
|
||
}
|
||
|
||
/**
|
||
* Display a stylized alert box.
|
||
*
|
||
* @param string|array<string> $string
|
||
* @param int|string|null $verbosity
|
||
*/
|
||
public function alert($string, $verbosity = null): void
|
||
{
|
||
if (\is_string($string) && \in_array($string, ['success', 'error', 'warning', 'info']) && $verbosity !== null && \is_string($verbosity)) {
|
||
$this->renderAlert($verbosity, $string);
|
||
|
||
return;
|
||
}
|
||
|
||
if (\is_array($string)) {
|
||
foreach ($string as $message) {
|
||
$this->renderAlert($message);
|
||
}
|
||
} else {
|
||
$this->renderAlert($string);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Render a stylized alert box with type detection.
|
||
*
|
||
* @param string $message The message to display
|
||
* @param string|null $type Optional alert type (success, error, warning, info)
|
||
*/
|
||
protected function renderAlert(string $message, ?string $type = null): void
|
||
{
|
||
if ($type === null) {
|
||
$detectedType = 'info';
|
||
|
||
if (preg_match('/^(success|error|warning|info):/i', $message, $matches)) {
|
||
$detectedType = strtolower($matches[1]);
|
||
$message = trim(substr($message, \strlen($matches[1]) + 1));
|
||
}
|
||
|
||
$type = $detectedType;
|
||
}
|
||
|
||
$style = match ($type) {
|
||
'success' => 'green',
|
||
'error' => 'red',
|
||
'warning' => 'yellow',
|
||
'info' => 'cyan',
|
||
default => 'white'
|
||
};
|
||
|
||
$icon = match ($type) {
|
||
'success' => '✓',
|
||
'error' => '✗',
|
||
'warning' => '⚠',
|
||
'info' => 'ℹ',
|
||
default => '•'
|
||
};
|
||
|
||
$this->io->newLine();
|
||
$this->io->writeln("<fg={$style}>{$icon} {$message} </>");
|
||
$this->io->newLine();
|
||
}
|
||
|
||
/**
|
||
* Display a task completion message.
|
||
*/
|
||
protected function taskCompleted(string $message = 'Done'): void
|
||
{
|
||
$this->success($message);
|
||
$this->io->newLine();
|
||
}
|
||
}
|