diff --git a/CHANGELOG.md b/CHANGELOG.md index 2101e39..471bf96 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/src/installer/installer b/src/installer/installer index f628c37..6cf9ee7 100644 --- a/src/installer/installer +++ b/src/installer/installer @@ -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 { diff --git a/src/ncc/Classes/NccExtension/NccCompiler.php b/src/ncc/Classes/NccExtension/NccCompiler.php index a1d1a98..ab9f115 100644 --- a/src/ncc/Classes/NccExtension/NccCompiler.php +++ b/src/ncc/Classes/NccExtension/NccCompiler.php @@ -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 diff --git a/src/ncc/Classes/PackageWriter.php b/src/ncc/Classes/PackageWriter.php index d65d58e..a89cff1 100644 --- a/src/ncc/Classes/PackageWriter.php +++ b/src/ncc/Classes/PackageWriter.php @@ -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); } /** diff --git a/src/ncc/Classes/PhpExtension/ExecutableCompiler.php b/src/ncc/Classes/PhpExtension/ExecutableCompiler.php index 80447e4..b115d8c 100644 --- a/src/ncc/Classes/PhpExtension/ExecutableCompiler.php +++ b/src/ncc/Classes/PhpExtension/ExecutableCompiler.php @@ -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); } } \ No newline at end of file diff --git a/src/ncc/Managers/PackageManager.php b/src/ncc/Managers/PackageManager.php index 9c8c130..25908df 100644 --- a/src/ncc/Managers/PackageManager.php +++ b/src/ncc/Managers/PackageManager.php @@ -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); diff --git a/src/ncc/Utilities/Console.php b/src/ncc/Utilities/Console.php index e8d58e8..4dd7550 100644 --- a/src/ncc/Utilities/Console.php +++ b/src/ncc/Utilities/Console.php @@ -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 * diff --git a/src/ncc/Utilities/ConsoleProgressBar.php b/src/ncc/Utilities/ConsoleProgressBar.php new file mode 100644 index 0000000..09ecaa6 --- /dev/null +++ b/src/ncc/Utilities/ConsoleProgressBar.php @@ -0,0 +1,393 @@ +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); + } + } + } \ No newline at end of file