- Added new ConsoleProgressBar class for UI improvement, imrpoved the CLI Progress Bar inspired by

[pacman](https://wiki.archlinux.org/title/pacman)
This commit is contained in:
Netkas 2023-10-17 21:23:05 -04:00
parent 173032df72
commit 2605b8d218
No known key found for this signature in database
GPG key ID: 5DAF58535614062B
8 changed files with 524 additions and 111 deletions

View file

@ -11,6 +11,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Implemented support in the AST traversal for the PHP statements `include`, `include_once`, `require`, and - Implemented support in the AST traversal for the PHP statements `include`, `include_once`, `require`, and
`require_once`. These statements are transformed into function calls. With this change, ncc can correctly handle and `require_once`. These statements are transformed into function calls. With this change, ncc can correctly handle and
import files from system packages or direct binary package files. import files from system packages or direct binary package files.
- Added new `ConsoleProgressBar` class for UI improvement, imrpoved the CLI Progress Bar inspired by
[pacman](https://wiki.archlinux.org/title/pacman)
### Fixed ### Fixed
- When finding package versions in the package lock, ncc will try to find a satisfying version rather than the exact - When finding package versions in the package lock, ncc will try to find a satisfying version rather than the exact

View file

@ -255,10 +255,12 @@
} }
$total_items = count($build_files); $total_items = count($build_files);
$processed_items = 1; //$processed_items = 1;
$progress_bar = new \ncc\Utilities\ConsoleProgressBar('Installing ncc', $total_items);
foreach ($build_files as $item) foreach ($build_files as $item)
{ {
$progress_bar->setMiscText($item, true);
$source = __DIR__ . DIRECTORY_SEPARATOR . $item; $source = __DIR__ . DIRECTORY_SEPARATOR . $item;
$destination = $NCC_INSTALL_PATH . DIRECTORY_SEPARATOR . $item; $destination = $NCC_INSTALL_PATH . DIRECTORY_SEPARATOR . $item;
@ -281,10 +283,14 @@
} }
} }
++$processed_items; //++$processed_items;
Console::inlineProgressBar($processed_items, $total_items); //Console::inlineProgressBar($processed_items, $total_items);
$progress_bar->increaseValue(1, true);
} }
$progress_bar->setMiscText('done', true);
unset($progress_bar);
// Initialize ncc's files // Initialize ncc's files
try try
{ {

View file

@ -47,6 +47,7 @@
use ncc\Objects\ProjectConfiguration\Dependency; use ncc\Objects\ProjectConfiguration\Dependency;
use ncc\Utilities\Base64; use ncc\Utilities\Base64;
use ncc\Utilities\Console; use ncc\Utilities\Console;
use ncc\Utilities\ConsoleProgressBar;
use ncc\Utilities\Functions; use ncc\Utilities\Functions;
use ncc\Utilities\IO; use ncc\Utilities\IO;
use ncc\Utilities\Resolver; use ncc\Utilities\Resolver;
@ -103,15 +104,15 @@
$package_path = ConstantCompiler::compileConstants($this->project_manager->getProjectConfiguration(), $configuration->getOutput()); $package_path = ConstantCompiler::compileConstants($this->project_manager->getProjectConfiguration(), $configuration->getOutput());
} }
$progress = 0; //$progress = 0;
$steps = $steps =
count($this->project_manager->getProjectConfiguration()->getExecutionPolicies()) + count($this->project_manager->getProjectConfiguration()->getExecutionPolicies()) +
count($this->project_manager->getComponents($build_configuration)) + count($this->project_manager->getComponents($build_configuration)) +
count($this->project_manager->getResources($build_configuration)); count($this->project_manager->getResources($build_configuration));
$progress_bar = new ConsoleProgressBar(sprintf('Building project \'%s\'', $this->project_manager->getProjectConfiguration()->getAssembly()->getName()), $steps);
$package_writer = $this->createPackageWriter($package_path, $configuration); $package_writer = $this->createPackageWriter($package_path, $configuration);
Console::out(sprintf('Building project \'%s\'', $this->project_manager->getProjectConfiguration()->getAssembly()->getName())); Console::outVerbose(sprintf('Building project \'%s\'', $this->project_manager->getProjectConfiguration()->getAssembly()->getName()));
if($static_dependencies) if($static_dependencies)
{ {
@ -155,39 +156,51 @@
if(count($execution_units) === 0) if(count($execution_units) === 0)
{ {
$progress = count($this->project_manager->getProjectConfiguration()->getExecutionPolicies()); //$progress = count($this->project_manager->getProjectConfiguration()->getExecutionPolicies());
Console::inlineProgressBar($progress, $steps); //Console::inlineProgressBar($progress, $steps);
$progress_bar->increaseValue(count($this->project_manager->getProjectConfiguration()->getExecutionPolicies()), true);
Console::outWarning('The project contains execution policies but none of them are used'); Console::outWarning('The project contains execution policies but none of them are used');
} }
foreach($execution_units as $unit) foreach($execution_units as $unit)
{ {
$progress++; $progress_bar->setMiscText($unit->getExecutionPolicy()->getName());
Console::inlineProgressBar($progress, $steps); //$progress++;
//Console::inlineProgressBar($progress, $steps);
$package_writer->addExecutionUnit($unit); $package_writer->addExecutionUnit($unit);
$progress_bar->increaseValue(1, true);
} }
} }
// Compile package components // Compile package components
foreach($this->project_manager->getComponents($build_configuration) as $component) foreach($this->project_manager->getComponents($build_configuration) as $component)
{ {
$progress++; //$progress++;
Console::inlineProgressBar($progress, $steps); //Console::inlineProgressBar($progress, $steps);
$progress_bar->setMiscText($component);
Console::outVerbose(sprintf('Compiling \'%s\'', $component)); Console::outVerbose(sprintf('Compiling \'%s\'', $component));
$this->processComponent($package_writer, $component); $this->processComponent($package_writer, $component);
$progress_bar->increaseValue(1, true);
} }
// Compile package resources // Compile package resources
foreach($this->project_manager->getResources($build_configuration) as $resource) foreach($this->project_manager->getResources($build_configuration) as $resource)
{ {
$progress++; //$progress++;
Console::inlineProgressBar($progress, $steps); //Console::inlineProgressBar($progress, $steps);
$progress_bar->setMiscText($resource);
Console::outVerbose(sprintf('Processing \'%s\'', $resource)); Console::outVerbose(sprintf('Processing \'%s\'', $resource));
$this->processResource($package_writer, $resource); $this->processResource($package_writer, $resource);
$progress_bar->increaseValue(1, true);
} }
$progress_bar->setMiscText('done', true);
unset($progress_bar);
Console::out(sprintf('Processing dependencies...'));
// Add the project dependencies // Add the project dependencies
foreach($this->project_manager->getProjectConfiguration()->getBuild()->getDependencies() as $dependency) foreach($this->project_manager->getProjectConfiguration()->getBuild()->getDependencies() as $dependency)
{ {
@ -211,6 +224,7 @@
* @param Dependency $dependency * @param Dependency $dependency
* @param bool $static * @param bool $static
* @return void * @return void
* @throws ConfigurationException
* @throws IOException * @throws IOException
*/ */
private function processDependency(PackageWriter $package_writer, Dependency $dependency, bool $static=false): void private function processDependency(PackageWriter $package_writer, Dependency $dependency, bool $static=false): void

View file

@ -29,6 +29,7 @@
use ncc\Enums\PackageDirectory; use ncc\Enums\PackageDirectory;
use ncc\Enums\PackageStructure; use ncc\Enums\PackageStructure;
use ncc\Enums\PackageStructureVersions; use ncc\Enums\PackageStructureVersions;
use ncc\Exceptions\ConfigurationException;
use ncc\Exceptions\IOException; use ncc\Exceptions\IOException;
use ncc\Objects\Package\Component; use ncc\Objects\Package\Component;
use ncc\Objects\Package\ExecutionUnit; use ncc\Objects\Package\ExecutionUnit;
@ -39,6 +40,7 @@
use ncc\Objects\ProjectConfiguration\Installer; use ncc\Objects\ProjectConfiguration\Installer;
use ncc\Extensions\ZiProto\ZiProto; use ncc\Extensions\ZiProto\ZiProto;
use ncc\Utilities\Console; use ncc\Utilities\Console;
use ncc\Utilities\ConsoleProgressBar;
class PackageWriter class PackageWriter
{ {
@ -357,13 +359,17 @@
* *
* @param PackageReader $reader * @param PackageReader $reader
* @return void * @return void
* @throws ConfigurationException
*/ */
public function merge(PackageReader $reader): void public function merge(PackageReader $reader): void
{ {
$progress_bar = new ConsoleProgressBar(sprintf('Merging %s', $reader->getAssembly()->getPackage()), count($reader->getDirectory()));
$processed_resources = []; $processed_resources = [];
foreach($reader->getDirectory() as $name => $pointer) foreach($reader->getDirectory() as $name => $pointer)
{ {
$progress_bar->setMiscText($name, true);
switch((int)substr(explode(':', $name, 2)[0], 1)) switch((int)substr(explode(':', $name, 2)[0], 1))
{ {
case PackageDirectory::METADATA: case PackageDirectory::METADATA:
@ -383,9 +389,13 @@
Console::outDebug(sprintf('Merging %s', $name)); Console::outDebug(sprintf('Merging %s', $name));
$processed_resources[$pointer] = $this->add($name, $reader->get($name)); $processed_resources[$pointer] = $this->add($name, $reader->get($name));
} }
$progress_bar->increaseValue(1, true);
} }
$progress_bar->setMiscText('done', true);
unset($progress_bar);
} }
/** /**

View file

@ -31,6 +31,7 @@
use ncc\ThirdParty\Symfony\Process\ExecutableFinder; use ncc\ThirdParty\Symfony\Process\ExecutableFinder;
use ncc\ThirdParty\Symfony\Process\Process; use ncc\ThirdParty\Symfony\Process\Process;
use ncc\Utilities\Console; use ncc\Utilities\Console;
use ncc\Utilities\ConsoleProgressBar;
use ncc\Utilities\Functions; use ncc\Utilities\Functions;
use ncc\Utilities\PathFinder; use ncc\Utilities\PathFinder;
@ -168,32 +169,35 @@
*/ */
private function hexDump(string $input_path, string $output_path, string $variable_name): void private function hexDump(string $input_path, string $output_path, string $variable_name): void
{ {
Console::out(sprintf('Processing %s to hex dump', $input_path)); Console::outVerbose(sprintf('Processing %s to hex dump', $input_path));
$input = fopen($input_path, 'rb'); $input = fopen($input_path, 'rb');
$output = fopen($output_path, 'wb'); $output = fopen($output_path, 'wb');
$byte_count = 0; $byte_count = 0;
$total_bytes = filesize($input_path); $progress_bar = new ConsoleProgressBar(sprintf('HexDump %s', $input_path), filesize($input_path));
fwrite($output, sprintf("unsigned char %s[] = {\n", Functions::toSnakeCase($variable_name)));
// Convert the binary data to hex and write it to the output file fwrite($output, sprintf("unsigned char %s[] = {\n", Functions::toSnakeCase($variable_name)));
// Convert the binary data to hex and write it to the output file using chunks
while (!feof($input)) while (!feof($input))
{ {
Console::inlineProgressBar(ftell($input), $total_bytes); $bytes = fread($input, 5026);
$len = strlen($bytes);
$byte = fread($input, 1); for ($i = 0; $i < $len; $i++)
if (strlen($byte) === 1)
{ {
fwrite($output, sprintf(" 0x%02x,", ord($byte))); fwrite($output, sprintf(" 0x%02x,", ord($bytes[$i])));
$byte_count++; $byte_count++;
// Write 12 bytes per line or when reaching the end of the file
if ($byte_count === 12 || ($i == $len - 1 && feof($input)))
{
fwrite($output, "\n");
$byte_count = 0;
}
} }
// Write 12 bytes per line or when reaching the end of the file $progress_bar->increaseValue($len, true);
if ($byte_count === 12 || feof($input)) $progress_bar->setMiscText(sprintf('Processed (%d/%d)', $progress_bar->getValue(), $progress_bar->getMaxValue()));
{
fwrite($output, "\n");
$byte_count = 0;
}
} }
// Close the output file // Close the output file
@ -204,5 +208,9 @@
// Finally, close the input and output files // Finally, close the input and output files
fclose($input); fclose($input);
fclose($output); fclose($output);
// Close the progress bar
$progress_bar->setMiscText('done', true);
unset($progress_bar);
} }
} }

View file

@ -56,6 +56,7 @@
use ncc\Objects\RemotePackageInput; use ncc\Objects\RemotePackageInput;
use ncc\ThirdParty\Symfony\Filesystem\Filesystem; use ncc\ThirdParty\Symfony\Filesystem\Filesystem;
use ncc\Utilities\Console; use ncc\Utilities\Console;
use ncc\Utilities\ConsoleProgressBar;
use ncc\Utilities\Functions; use ncc\Utilities\Functions;
use ncc\Utilities\IO; use ncc\Utilities\IO;
use ncc\Utilities\PathFinder; use ncc\Utilities\PathFinder;
@ -643,6 +644,7 @@
* @return void * @return void
* @throws ConfigurationException * @throws ConfigurationException
* @throws IOException * @throws IOException
* @throws NotSupportedException
* @throws OperationException * @throws OperationException
*/ */
private function extractPackageContents(PackageReader $package_reader, string $package_path): void private function extractPackageContents(PackageReader $package_reader, string $package_path): void
@ -654,61 +656,88 @@
count($package_reader->getResources()) + count($package_reader->getResources()) +
count($package_reader->getExecutionUnits()) + count($package_reader->getExecutionUnits()) +
6; 6;
$current_step = 0; //$current_step = 0;
$progress_bar = new ConsoleProgressBar(sprintf('Extracting package %s=%s', $package_reader->getAssembly()->getPackage(), $package_reader->getAssembly()->getVersion()), $total_steps);
Console::inlineProgressBar(++$current_step, $total_steps); //Console::inlineProgressBar(++$current_step, $total_steps);
$progress_bar->increaseValue(1, true);
foreach($package_reader->getComponents() as $component_name) foreach($package_reader->getComponents() as $component_name)
{ {
$progress_bar->setMiscText($component_name);
if(Resolver::checkLogLevel(LogLevel::VERBOSE, Main::getLogLevel()))
{
Console::outVerbose(sprintf('Extracting component %s to %s', $component_name, $bin_path . DIRECTORY_SEPARATOR . $component_name));
}
IO::fwrite( IO::fwrite(
$bin_path . DIRECTORY_SEPARATOR . $component_name, $bin_path . DIRECTORY_SEPARATOR . $component_name,
$package_reader->getComponent($component_name)->getData([ComponentDecodeOptions::AS_FILE]), 0755 $package_reader->getComponent($component_name)->getData([ComponentDecodeOptions::AS_FILE]), 0755
); );
Console::inlineProgressBar(++$current_step, $total_steps); //Console::inlineProgressBar(++$current_step, $total_steps);
$progress_bar->increaseValue(1, true);
} }
foreach($package_reader->getResources() as $resource_name) foreach($package_reader->getResources() as $resource_name)
{ {
IO::fwrite( $progress_bar->setMiscText($resource_name);
$bin_path . DIRECTORY_SEPARATOR . $resource_name,
$package_reader->getResource($resource_name)->getData(), 0755
);
if(Resolver::checkLogLevel(LogLevel::VERBOSE, Main::getLogLevel())) if(Resolver::checkLogLevel(LogLevel::VERBOSE, Main::getLogLevel()))
{ {
Console::outVerbose(sprintf('Extracting resource %s to %s', $resource_name, $bin_path . DIRECTORY_SEPARATOR . $resource_name)); Console::outVerbose(sprintf('Extracting resource %s to %s', $resource_name, $bin_path . DIRECTORY_SEPARATOR . $resource_name));
} }
Console::inlineProgressBar(++$current_step, $total_steps); IO::fwrite(
$bin_path . DIRECTORY_SEPARATOR . $resource_name,
$package_reader->getResource($resource_name)->getData(), 0755
);
//Console::inlineProgressBar(++$current_step, $total_steps);
$progress_bar->increaseValue(1, true);
} }
foreach($package_reader->getExecutionUnits() as $unit) foreach($package_reader->getExecutionUnits() as $unit)
{ {
$progress_bar->setMiscText($unit);
if(Resolver::checkLogLevel(LogLevel::VERBOSE, Main::getLogLevel()))
{
Console::outVerbose(sprintf('Extracting execution unit %s to %s', $unit, $package_path . DIRECTORY_SEPARATOR . 'units' . DIRECTORY_SEPARATOR . $package_reader->getExecutionUnit($unit)->getExecutionPolicy()->getName() . '.unit'));
}
$execution_unit = $package_reader->getExecutionUnit($unit); $execution_unit = $package_reader->getExecutionUnit($unit);
$unit_path = $package_path . DIRECTORY_SEPARATOR . 'units' . DIRECTORY_SEPARATOR . $execution_unit->getExecutionPolicy()->getName() . '.unit'; $unit_path = $package_path . DIRECTORY_SEPARATOR . 'units' . DIRECTORY_SEPARATOR . $execution_unit->getExecutionPolicy()->getName() . '.unit';
IO::fwrite($unit_path, ZiProto::encode($execution_unit->toArray(true)), 0755); IO::fwrite($unit_path, ZiProto::encode($execution_unit->toArray(true)), 0755);
Console::inlineProgressBar(++$current_step, $total_steps); //Console::inlineProgressBar(++$current_step, $total_steps);
$progress_bar->increaseValue(1, true);
} }
$class_map = []; $class_map = [];
foreach($package_reader->getClassMap() as $class) foreach($package_reader->getClassMap() as $class)
{ {
$progress_bar->setMiscText($class);
$class_map[$class] = $package_reader->getComponentByClass($class)->getName(); $class_map[$class] = $package_reader->getComponentByClass($class)->getName();
} }
Console::inlineProgressBar(++$current_step, $total_steps); //Console::inlineProgressBar(++$current_step, $total_steps);
$progress_bar->increaseValue(1, true);
if($package_reader->getInstaller() !== null) if($package_reader->getInstaller() !== null)
{ {
$progress_bar->setMiscText('installer');
IO::fwrite($package_path . DIRECTORY_SEPARATOR . FileDescriptor::INSTALLER, ZiProto::encode($package_reader->getInstaller()?->toArray(true))); IO::fwrite($package_path . DIRECTORY_SEPARATOR . FileDescriptor::INSTALLER, ZiProto::encode($package_reader->getInstaller()?->toArray(true)));
} }
Console::inlineProgressBar(++$current_step, $total_steps); //Console::inlineProgressBar(++$current_step, $total_steps);
$progress_bar->increaseValue(1, true);
if(count($class_map) > 0) if(count($class_map) > 0)
{ {
$progress_bar->setMiscText('class map');
IO::fwrite($package_path . DIRECTORY_SEPARATOR . FileDescriptor::CLASS_MAP, ZiProto::encode($class_map)); IO::fwrite($package_path . DIRECTORY_SEPARATOR . FileDescriptor::CLASS_MAP, ZiProto::encode($class_map));
} }
Console::inlineProgressBar(++$current_step, $total_steps); //Console::inlineProgressBar(++$current_step, $total_steps);
$progress_bar->increaseValue(1, true);
IO::fwrite($package_path . DIRECTORY_SEPARATOR . FileDescriptor::ASSEMBLY, ZiProto::encode($package_reader->getAssembly()->toArray(true))); IO::fwrite($package_path . DIRECTORY_SEPARATOR . FileDescriptor::ASSEMBLY, ZiProto::encode($package_reader->getAssembly()->toArray(true)));
IO::fwrite($package_path . DIRECTORY_SEPARATOR . FileDescriptor::METADATA, ZiProto::encode($package_reader->getMetadata()->toArray(true))); IO::fwrite($package_path . DIRECTORY_SEPARATOR . FileDescriptor::METADATA, ZiProto::encode($package_reader->getMetadata()->toArray(true)));
@ -717,11 +746,16 @@
{ {
IO::fwrite($package_path . DIRECTORY_SEPARATOR . FileDescriptor::UPDATE, ZiProto::encode($package_reader->getMetadata()->getUpdateSource()?->toArray(true))); IO::fwrite($package_path . DIRECTORY_SEPARATOR . FileDescriptor::UPDATE, ZiProto::encode($package_reader->getMetadata()->getUpdateSource()?->toArray(true)));
} }
//Console::inlineProgressBar(++$current_step, $total_steps);
$progress_bar->increaseValue(1, true);
Console::inlineProgressBar(++$current_step, $total_steps); $progress_bar->setMiscText('creating shadowcopy', true);
$package_reader->saveCopy($package_path . DIRECTORY_SEPARATOR . FileDescriptor::SHADOW_PACKAGE); $package_reader->saveCopy($package_path . DIRECTORY_SEPARATOR . FileDescriptor::SHADOW_PACKAGE);
Console::inlineProgressBar(++$current_step, $total_steps); //Console::inlineProgressBar(++$current_step, $total_steps);
$progress_bar->setMiscText('done', true);
$progress_bar->increaseValue(1, true);
unset($progress_bar);
} }
/** /**
@ -851,6 +885,7 @@
$file_handle = fopen($file_path, 'wb'); $file_handle = fopen($file_path, 'wb');
$end = false; $end = false;
$progress_bar = new ConsoleProgressBar(sprintf('Downloading %s', $url), 100);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, false); curl_setopt($curl, CURLOPT_RETURNTRANSFER, false);
curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true); curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true);
@ -859,34 +894,39 @@
curl_setopt($curl, CURLOPT_HTTPHEADER, [ curl_setopt($curl, CURLOPT_HTTPHEADER, [
'User-Agent: ncc' 'User-Agent: ncc'
]); ]);
curl_setopt($curl, CURLOPT_PROGRESSFUNCTION, static function ($resource, $downloadSize, $downloaded) use ($url, &$end) curl_setopt($curl, CURLOPT_PROGRESSFUNCTION, static function ($resource, $download_size, $downloaded) use ($url, &$end, $progress_bar)
{ {
if($downloadSize === $downloaded && $end) if($download_size === $downloaded && $end)
{ {
return; return;
} }
if($downloadSize === 0) if($download_size === 0)
{ {
return; return;
} }
if(Resolver::checkLogLevel(LogLevel::VERBOSE, Main::getLogLevel())) if(Resolver::checkLogLevel(LogLevel::VERBOSE, Main::getLogLevel()))
{ {
$percentage = round(($downloaded / $downloadSize) * 100, 2); $percentage = round(($downloaded / $download_size) * 100, 2);
Console::out(sprintf('Download progress %s (%s/%s) for %s', $percentage, $downloaded, $downloadSize, $url)); Console::out(sprintf('Download progress %s (%s/%s) for %s', $percentage, $downloaded, $download_size, $url));
} }
else else
{ {
Console::inlineProgressBar($downloaded, $downloadSize); $progress_bar->setMaxValue($download_size);
$progress_bar->setValue($downloaded);
$progress_bar->setMiscText(sprintf('%s/%s', $downloaded, $download_size));
$progress_bar->update();
} }
if($downloadSize === $downloaded) if($download_size === $downloaded)
{ {
$end = true; $end = true;
} }
}); });
unset($progress_bar);
curl_exec($curl); curl_exec($curl);
fclose($file_handle); fclose($file_handle);

View file

@ -43,66 +43,6 @@
*/ */
private static $last_tick_time; private static $last_tick_time;
/**
* Inline Progress bar, created by dealnews.com.
*
* @param int $value
* @param int $total
* @param int $size
* @param array $options
* @return void
* @copyright Copyright (c) 2010, dealnews.com, Inc. All rights reserved.
* @copyright Copyright (c) 2023, Nosial. All rights reserved
*/
public static function inlineProgressBar(int $value, int $total, int $size = 10, array $options = []): void
{
if(Resolver::checkLogLevel(LogLevel::VERBOSE, Main::getLogLevel()))
{
return;
}
static $start_time;
// Start time initialization
if (!$start_time)
{
$start_time = time();
}
// If the value is out of bounds or zero, return early
if ($value > $total || $value === 0)
{
return;
}
// Build status bar
$percentage = $value / $total;
$barLength = floor($percentage * $size);
$statusBar = "\r[ "
. str_repeat("=", $barLength)
. ($barLength < $size ? ">" : "=")
. str_repeat(" ", $size - $barLength)
. " ] "
. number_format($percentage * 100) . "% $value/$total";
// ETA and elapsed time calculation
$rate = (time() - $start_time) / $value;
$eta = round($rate * ($total - $value), 2);
$elapsed = time() - $start_time;
$remaining_text = $options['remaining_text'] ?? 'remaining: ';
$statusBar .= " $remaining_text " . number_format($eta) . " sec. elapsed: " . number_format($elapsed) . " sec.";
print("$statusBar ");
flush();
// Reset variables once the progress is complete
if ($value === $total)
{
print("\n");
$start_time = null; // This resets the start time for the next progress bar
}
}
/** /**
* Appends a verbose prefix to the message * Appends a verbose prefix to the message
* *

View file

@ -0,0 +1,393 @@
<?php
/** @noinspection PhpMissingFieldTypeInspection */
/*
* Copyright (c) Nosial 2022-2023, all rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
* associated documentation files (the "Software"), to deal in the Software without restriction, including without
* limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions
* of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
* PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*
*/
namespace ncc\Utilities;
class ConsoleProgressBar
{
/**
* @var int
*/
private $value;
/**
* @var int
*/
private $max_value;
/**
* @var int
*/
private $terminal_width;
/**
* @var int
*/
private $progress_width;
/**
* @var bool
*/
private $ended;
/**
* @var string
*/
private $title;
/**
* @var string|null
*/
private $misc_text;
/**
* @var bool
*/
private $auto_end;
/**
* Constructor for the object.
*
* This constructor initializes the object with the given parameters. By default,
* the $max_value parameter is set to 100, and the $progress_width parameter is set to 20.
* The object's title is set to the given $title value, and other properties are initialized
* accordingly.
*
* @param string $title The title to be set for the object.
* @param int $max_value Optional. The maximum value for the object's progress. Defaults to 100.
* @param bool $auto_end Optional. If True, when the progress bar reaches the $max_value, a new line is created.
* @param int $progress_width Optional. The width of the progress bar in characters. Defaults to 20.
* @return void
*/
public function __construct(string $title, int $max_value=100, bool $auto_end=false, int $progress_width=20)
{
$this->title = $title;
$this->progress_width = $progress_width;
$this->value = 0;
$this->max_value = $max_value;
$this->ended = false;
$this->auto_end = $auto_end;
$this->terminal_width = $this->getTerminalWidth();
}
/**
* Get the title of the object.
*
* @return string The title of the object.
*/
public function getTitle(): string
{
return $this->title;
}
/**
* Sets the title of the object.
*
* This method sets the title of the object to the given value. Optionally,
* it can also trigger an update if the $update parameter is set to true.
*
* @param string $title The new title to be set.
* @param bool $update Optional. Whether to trigger an update after setting the title.
* Defaults to false.
* @return void
*/
public function setTitle(string $title, bool $update=false): void
{
$this->title = $title;
if($update)
{
$this->update();
}
}
/**
* Retrieves the miscellaneous text.
*
* This method retrieves the miscellaneous text associated with the object.
*
* @return string|null The miscellaneous text, or null if it is not set.
*/
public function getMiscText(): ?string
{
return $this->misc_text;
}
/**
* Sets the miscellaneous text of the object.
*
* This method sets the miscellaneous text of the object to the given value. Optionally,
* it can also trigger an update if the $update parameter is set to true.
*
* @param string|null $misc_text The new miscellaneous text to be set. If null, the miscellaneous text will be cleared.
* @param bool $update Optional. Whether to trigger an update after setting the miscellaneous text.
* Defaults to false.
* @return void
*/
public function setMiscText(?string $misc_text, bool $update=false): void
{
$this->misc_text = $misc_text;
if($update)
{
$this->update();
}
}
/**
* Gets the value of the object.
*
* This method retrieves the value of the object.
*
* @return int The value of the object.
*/
public function getValue(): int
{
return $this->value;
}
/**
* Sets the value of the object.
*
* This method sets the value of the object to the given value. Optionally,
* it can also trigger an update if the $update parameter is set to true.
* If the given value is greater than the maximum value, it will be set to
* the maximum value. If the given value is less than 0, it will be set to 0.
*
* @param int $value The new value to be set.
* @param bool $update Optional. Whether to trigger an update after setting the value.
* Defaults to false.
* @return void
*/
public function setValue(int $value, bool $update=false): void
{
if($value > $this->max_value)
{
$value = $this->max_value;
}
elseif($value < 0)
{
$value = 0;
}
$this->value = $value;
if($update)
{
$this->update();
}
}
/**
* Increases the value of the object by the given amount.
*
* This method increases the current value of the object by the specified amount.
* Optionally, it can also trigger an update if the $update parameter is set to true.
*
* @param int $value The amount by which to increase the value.
* @param bool $update Optional. Whether to trigger an update after increasing the value.
* Defaults to false.
* @return void
*/
public function increaseValue(int $value=1, bool $update=false): void
{
$this->setValue($this->value + $value, $update);
}
/**
* Decreases the value of the object by the given amount.
*
* This method decreases the value of the object by the specified amount.
* Optionally, it can also trigger an update if the $update parameter is set to true.
*
* @param int $value The amount to decrease the value by.
* @param bool $update Optional. Whether to trigger an update after decreasing the value.
* Defaults to false.
* @return void
*/
public function decreaseValue(int $value, bool $update=false): void
{
$this->setValue($this->value - $value, $update);
}
/**
* Retrieves the maximum value.
*
* This method returns the current maximum value stored in the object.
*
* @return int The maximum value.
*/
public function getMaxValue(): int
{
return $this->max_value;
}
/**
* Sets the maximum value of the object.
*
* This method sets the maximum value of the object to the given value. Optionally,
* it can also trigger an update if the $update parameter is set to true.
* If the given $max_value is negative, it is set to 0.
*
* @param int $max_value The new maximum value to be set.
* @param bool $update Optional. Whether to trigger an update after setting the maximum value.
* Defaults to false.
* @return void
*/
public function setMaxValue(int $max_value, bool $update=false): void
{
if($max_value < 0)
{
$max_value = 0;
}
$this->max_value = $max_value;
if($update)
{
$this->update();
}
}
/**
* Updates the object's state.
*
* This method updates the state of the object based on its current value and max value.
* If the current value is greater than or equal to the max value, it prints the information
* and progress bar using the renderInformation() and renderProgressBar() methods respectively,
* and sets the 'ended' flag to true.
* If the current value is less than the max value, it prints the information and progress bar
* using the renderInformation() and renderProgressBar() methods respectively, but without
* printing a new line.
*
* @return void
*/
public function update(): void
{
if($this->auto_end && $this->value >= $this->max_value)
{
print($this->renderInformation() . $this->renderProgressBar() . "\n");
$this->ended = true;
}
else
{
print($this->renderInformation() . $this->renderProgressBar() . "\r");
}
}
/**
* Retrieves the width of the terminal.
*
* This method retrieves the width of the terminal by executing the 'tput cols' command.
* If the command execution fails or the output is empty, a default width of 70 is returned.
*
* @return int The width of the terminal.
*/
private function getTerminalWidth(): int
{
exec('tput cols', $output, $result);
if(empty($output[0]) || $result !== 0)
{
return 70;
}
return (int)$output[0];
}
/**
* Renders the information to be displayed.
*
* This method calculates and returns a string containing the rendered information
* based on the current state of the object. The information includes the title,
* optional miscellaneous text, and any required spacing and formatting for display.
*
* @return string The rendered information string.
*/
private function renderInformation(): string
{
// Resize title and misc if the terminal width is too small
$max_text_length = $this->terminal_width - $this->progress_width - 10;
if(strlen($this->title . ' ' . ($this->misc_text ?? '')) > $max_text_length)
{
// Calculate the maximum length of title and misc and assign them new truncated values
$new_title_length = floor($max_text_length * strlen($this->title) / (strlen($this->title) + strlen($this->misc_text)));
$title = substr($this->title, 0, $new_title_length);
$misc = substr($this->misc_text, 0, ($max_text_length - $new_title_length));
}
else
{
$title = $this->title;
$misc = $this->misc_text;
}
$spaces = $this->terminal_width - strlen($title) - strlen($misc) - $this->progress_width - 10;
$line = $title . str_repeat(' ', $spaces);
if (!empty($misc))
{
$line .= ' ' . $misc;
}
return $line;
}
/**
* Renders the progress bar as a string.
*
* This method calculates the number of hashes and dashes based on the current
* progress and width of the progress bar. It also calculates the percentage
* completed and formats it with two decimal places at the end. It then constructs
* and returns the progress bar string.
*
* @return string The progress bar string.
*/
private function renderProgressBar(): string
{
$hashes_count = round($this->progress_width * $this->value / $this->max_value);
$dashes_count = $this->progress_width - $hashes_count;
$percent_done = round($this->value * 100 / $this->max_value);
return ' [' . str_repeat('#', $hashes_count) . str_repeat('-', $dashes_count) . ']' . sprintf('%4s', $percent_done) . '%';
}
/**
* Destructor for the object.
*
* This method is automatically called when the object is destroyed. It checks
* if the object has already ended and if not, it prints a new line character.
*
* @return void
*/
public function __destruct()
{
if(!$this->ended)
{
print(PHP_EOL);
}
}
}