- 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
`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.
- Added new `ConsoleProgressBar` class for UI improvement, imrpoved the CLI Progress Bar inspired by
[pacman](https://wiki.archlinux.org/title/pacman)
### Fixed
- 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);
$processed_items = 1;
//$processed_items = 1;
$progress_bar = new \ncc\Utilities\ConsoleProgressBar('Installing ncc', $total_items);
foreach ($build_files as $item)
{
$progress_bar->setMiscText($item, true);
$source = __DIR__ . DIRECTORY_SEPARATOR . $item;
$destination = $NCC_INSTALL_PATH . DIRECTORY_SEPARATOR . $item;
@ -281,10 +283,14 @@
}
}
++$processed_items;
Console::inlineProgressBar($processed_items, $total_items);
//++$processed_items;
//Console::inlineProgressBar($processed_items, $total_items);
$progress_bar->increaseValue(1, true);
}
$progress_bar->setMiscText('done', true);
unset($progress_bar);
// Initialize ncc's files
try
{

View file

@ -47,6 +47,7 @@
use ncc\Objects\ProjectConfiguration\Dependency;
use ncc\Utilities\Base64;
use ncc\Utilities\Console;
use ncc\Utilities\ConsoleProgressBar;
use ncc\Utilities\Functions;
use ncc\Utilities\IO;
use ncc\Utilities\Resolver;
@ -103,15 +104,15 @@
$package_path = ConstantCompiler::compileConstants($this->project_manager->getProjectConfiguration(), $configuration->getOutput());
}
$progress = 0;
//$progress = 0;
$steps =
count($this->project_manager->getProjectConfiguration()->getExecutionPolicies()) +
count($this->project_manager->getComponents($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);
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)
{
@ -155,39 +156,51 @@
if(count($execution_units) === 0)
{
$progress = count($this->project_manager->getProjectConfiguration()->getExecutionPolicies());
Console::inlineProgressBar($progress, $steps);
//$progress = count($this->project_manager->getProjectConfiguration()->getExecutionPolicies());
//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');
}
foreach($execution_units as $unit)
{
$progress++;
Console::inlineProgressBar($progress, $steps);
$progress_bar->setMiscText($unit->getExecutionPolicy()->getName());
//$progress++;
//Console::inlineProgressBar($progress, $steps);
$package_writer->addExecutionUnit($unit);
$progress_bar->increaseValue(1, true);
}
}
// Compile package components
foreach($this->project_manager->getComponents($build_configuration) as $component)
{
$progress++;
Console::inlineProgressBar($progress, $steps);
//$progress++;
//Console::inlineProgressBar($progress, $steps);
$progress_bar->setMiscText($component);
Console::outVerbose(sprintf('Compiling \'%s\'', $component));
$this->processComponent($package_writer, $component);
$progress_bar->increaseValue(1, true);
}
// Compile package resources
foreach($this->project_manager->getResources($build_configuration) as $resource)
{
$progress++;
Console::inlineProgressBar($progress, $steps);
//$progress++;
//Console::inlineProgressBar($progress, $steps);
$progress_bar->setMiscText($resource);
Console::outVerbose(sprintf('Processing \'%s\'', $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
foreach($this->project_manager->getProjectConfiguration()->getBuild()->getDependencies() as $dependency)
{
@ -211,6 +224,7 @@
* @param Dependency $dependency
* @param bool $static
* @return void
* @throws ConfigurationException
* @throws IOException
*/
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\PackageStructure;
use ncc\Enums\PackageStructureVersions;
use ncc\Exceptions\ConfigurationException;
use ncc\Exceptions\IOException;
use ncc\Objects\Package\Component;
use ncc\Objects\Package\ExecutionUnit;
@ -39,6 +40,7 @@
use ncc\Objects\ProjectConfiguration\Installer;
use ncc\Extensions\ZiProto\ZiProto;
use ncc\Utilities\Console;
use ncc\Utilities\ConsoleProgressBar;
class PackageWriter
{
@ -357,13 +359,17 @@
*
* @param PackageReader $reader
* @return void
* @throws ConfigurationException
*/
public function merge(PackageReader $reader): void
{
$progress_bar = new ConsoleProgressBar(sprintf('Merging %s', $reader->getAssembly()->getPackage()), count($reader->getDirectory()));
$processed_resources = [];
foreach($reader->getDirectory() as $name => $pointer)
{
$progress_bar->setMiscText($name, true);
switch((int)substr(explode(':', $name, 2)[0], 1))
{
case PackageDirectory::METADATA:
@ -383,9 +389,13 @@
Console::outDebug(sprintf('Merging %s', $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\Process;
use ncc\Utilities\Console;
use ncc\Utilities\ConsoleProgressBar;
use ncc\Utilities\Functions;
use ncc\Utilities\PathFinder;
@ -168,32 +169,35 @@
*/
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');
$output = fopen($output_path, 'wb');
$byte_count = 0;
$total_bytes = filesize($input_path);
fwrite($output, sprintf("unsigned char %s[] = {\n", Functions::toSnakeCase($variable_name)));
$progress_bar = new ConsoleProgressBar(sprintf('HexDump %s', $input_path), filesize($input_path));
// 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))
{
Console::inlineProgressBar(ftell($input), $total_bytes);
$bytes = fread($input, 5026);
$len = strlen($bytes);
$byte = fread($input, 1);
if (strlen($byte) === 1)
for ($i = 0; $i < $len; $i++)
{
fwrite($output, sprintf(" 0x%02x,", ord($byte)));
fwrite($output, sprintf(" 0x%02x,", ord($bytes[$i])));
$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
if ($byte_count === 12 || feof($input))
{
fwrite($output, "\n");
$byte_count = 0;
}
$progress_bar->increaseValue($len, true);
$progress_bar->setMiscText(sprintf('Processed (%d/%d)', $progress_bar->getValue(), $progress_bar->getMaxValue()));
}
// Close the output file
@ -204,5 +208,9 @@
// Finally, close the input and output files
fclose($input);
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\ThirdParty\Symfony\Filesystem\Filesystem;
use ncc\Utilities\Console;
use ncc\Utilities\ConsoleProgressBar;
use ncc\Utilities\Functions;
use ncc\Utilities\IO;
use ncc\Utilities\PathFinder;
@ -643,6 +644,7 @@
* @return void
* @throws ConfigurationException
* @throws IOException
* @throws NotSupportedException
* @throws OperationException
*/
private function extractPackageContents(PackageReader $package_reader, string $package_path): void
@ -654,61 +656,88 @@
count($package_reader->getResources()) +
count($package_reader->getExecutionUnits()) +
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)
{
$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(
$bin_path . DIRECTORY_SEPARATOR . $component_name,
$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)
{
IO::fwrite(
$bin_path . DIRECTORY_SEPARATOR . $resource_name,
$package_reader->getResource($resource_name)->getData(), 0755
);
$progress_bar->setMiscText($resource_name);
if(Resolver::checkLogLevel(LogLevel::VERBOSE, Main::getLogLevel()))
{
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)
{
$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);
$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);
Console::inlineProgressBar(++$current_step, $total_steps);
//Console::inlineProgressBar(++$current_step, $total_steps);
$progress_bar->increaseValue(1, true);
}
$class_map = [];
foreach($package_reader->getClassMap() as $class)
{
$progress_bar->setMiscText($class);
$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)
{
$progress_bar->setMiscText('installer');
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)
{
$progress_bar->setMiscText('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::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)));
}
//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);
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');
$end = false;
$progress_bar = new ConsoleProgressBar(sprintf('Downloading %s', $url), 100);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, false);
curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true);
@ -859,34 +894,39 @@
curl_setopt($curl, CURLOPT_HTTPHEADER, [
'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;
}
if($downloadSize === 0)
if($download_size === 0)
{
return;
}
if(Resolver::checkLogLevel(LogLevel::VERBOSE, Main::getLogLevel()))
{
$percentage = round(($downloaded / $downloadSize) * 100, 2);
Console::out(sprintf('Download progress %s (%s/%s) for %s', $percentage, $downloaded, $downloadSize, $url));
$percentage = round(($downloaded / $download_size) * 100, 2);
Console::out(sprintf('Download progress %s (%s/%s) for %s', $percentage, $downloaded, $download_size, $url));
}
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;
}
});
unset($progress_bar);
curl_exec($curl);
fclose($file_handle);

View file

@ -43,66 +43,6 @@
*/
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
*

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);
}
}
}