Compare commits

...

17 commits

Author SHA1 Message Date
Norbert Wagner
cedd316946 Passing the separator after the array is no longer supported 2023-03-23 08:50:28 +01:00
Alannah Kearney
1992fee96b fix: Add use clause for Exception so which() can catch RunCommandFailedException correctly 2021-09-07 11:39:39 +10:00
Alannah Kearney
b9a3034788 chore: Update README, LICENCE, and add code linting 2021-07-29 14:37:32 +10:00
Alannah Kearney
33090aee1f feat: Add which() method and refactor run_command() 2021-07-29 14:36:51 +10:00
Alannah Kearney
630efc99d6 Fixed call to implode() in display_error_and_exit() to avoid deprecation warning. 2020-04-11 18:51:09 +10:00
Alannah Kearney
593410a68b Updated README, CHANGELOG, and LICENCE for 1.1.9 release 2020-04-06 17:24:04 +10:00
Alannah Kearney
3ccfe2477f Added run_command() function and RunCommandFailedException exception 2020-04-06 17:23:44 +10:00
Alannah Kearney
feb7956bb0 Added helpers-exceptions-readabletrace package 2020-04-06 17:23:18 +10:00
Alannah Kearney
38b75f4d8e Removed version number from composer.json. No longer requiring phpunit since currently not being used. 2019-11-28 03:18:16 +00:00
Alannah Kearney
ace7c68ed6 Updated README and CHANGELOG for 1.1.8 release 2019-06-01 23:04:07 +10:00
Alannah Kearney
0807b58647 Using v1.2.x of pointybeard/helpers-cli-input. Updated version constraints. Bumped version to 1.1.8 2019-06-01 23:02:15 +10:00
Alannah Kearney
f605849ee0 Updated manpage() to work with pointybeard/helpers-cli-input 1.2 2019-06-01 15:02:30 +10:00
Alannah Kearney
2274998ed6 Upated README, CHANGELOG, and composer.json for 1.1.7 release 2019-05-26 20:22:01 +10:00
Alannah Kearney
558ea1ba2c Using readable_debug_backtrace (provided by pointybeard/helpers-functions-debug) to produce a trace if one is provided 2019-05-26 20:19:25 +10:00
Alannah Kearney
9f9e6cb397 Added pointybeard/helpers-functions-debug package 2019-05-26 20:16:27 +10:00
Alannah Kearney
4c33ccfc23 Updated composer.json, README, and CHANGELOG for 1.1.6 release 2019-05-25 14:29:34 +10:00
Alannah Kearney
5de5730739 Added display_error_and_exit function 2019-05-25 14:27:28 +10:00
9 changed files with 471 additions and 83 deletions

2
.gitattributes vendored Normal file
View file

@ -0,0 +1,2 @@
# Force LF
* text eol=lf

41
.php-commitizen.php Normal file
View file

@ -0,0 +1,41 @@
<?php
declare(strict_types=1);
return [
'type' => [
'lengthMin' => 3,
'lengthMax' => 8,
'acceptExtra' => false,
'values' => [
'feat',
'fix',
'docs',
'chore',
'test',
'refactor',
'revert',
'ci',
]
],
'scope' => [
'lengthMin' => 0,
'lengthMax' => 10,
'acceptExtra' => true,
'values' => [],
],
'description' => [
'lengthMin' => 1,
'lengthMax' => 47,
],
'subject' => [
'lengthMin' => 1,
'lengthMax' => 69,
],
'body' => [
'wrap' => 72,
],
'footer' => [
'wrap' => 72,
],
];

59
.php-cs-fixer.dist.php Normal file
View file

@ -0,0 +1,59 @@
<?php
declare(strict_types=1);
$header = <<<EOF
This file is part of the "PHP Helpers: Command-line Functions" repository.
Copyright 2019-2021 Alannah Kearney <hi@alannahkearney.com>
For the full copyright and license information, please view the LICENCE
file that was distributed with this source code.
EOF;
return (new PhpCsFixer\Config())
->setUsingCache(true)
->setRiskyAllowed(true)
->setFinder(
(new PhpCsFixer\Finder())
->files()
->name('*.php')
->in(__DIR__)
->exclude(__DIR__.'/vendor')
)
->setRules([
'@PSR2' => true,
'@Symfony' => true,
'is_null' => true,
'blank_line_before_statement' => ['statements' => ['continue', 'declare', 'return', 'throw', 'try']],
'cast_spaces' => ['space' => 'single'],
'header_comment' => ['header' => $header],
'include' => true,
'class_attributes_separation' => ['elements' => ['const' => 'one', 'method' => 'one', 'property' => 'one']],
'no_blank_lines_after_class_opening' => true,
'no_blank_lines_after_phpdoc' => true,
'no_empty_statement' => true,
'no_extra_blank_lines' => true,
'no_leading_import_slash' => true,
'no_leading_namespace_whitespace' => true,
'no_trailing_comma_in_singleline_array' => true,
'no_unused_imports' => true,
'no_whitespace_in_blank_line' => true,
'object_operator_without_whitespace' => true,
'phpdoc_align' => true,
'phpdoc_indent' => true,
'phpdoc_no_access' => true,
'phpdoc_no_package' => true,
'phpdoc_order' => true,
'phpdoc_scalar' => true,
'phpdoc_trim' => true,
'phpdoc_types' => true,
'psr_autoloading' => true,
'array_syntax' => ['syntax' => 'short'],
'declare_strict_types' => true,
'single_blank_line_before_namespace' => true,
'standardize_not_equals' => true,
'ternary_operator_spaces' => true,
'trailing_comma_in_multiline' => true,
])
;

View file

@ -3,7 +3,35 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](http://semver.org/). This project adheres to [Semantic Versioning](http://semver.org/).
**View all [Unreleased][] changes here** ## [1.1.10][]
#### Added
- Added `which()` function
#### Changed
- Refactor of `run_command()` function to return an exit code
## [1.1.9][]
#### Added
- Added `run_command()` function
- Added `RunCommandFailedException` exception
- Added `pointybeard/helpers-exceptions-readabletrace` package
## [1.1.8][]
#### Changed
- Updated `manpage()` to work with `pointybeard/helpers-cli-input` 1.2
- Using v1.2.x of `pointybeard/helpers-cli-input`
- Updated version constraints in `composer.json`
## [1.1.7][]
#### Added
- Added `pointybeard/helpers-functions-debug` package
#### Changed
- Using `readable_debug_backtrace()` (provided by `pointybeard/helpers-functions-debug`) in `display_error_and_exit()` to produce a trace if one is provided
## [1.1.6][]
#### Added
- Added `display_error_and_exit` function
## [1.1.5][] ## [1.1.5][]
#### Changed #### Changed
@ -11,11 +39,11 @@ This project adheres to [Semantic Versioning](http://semver.org/).
## [1.1.4][] ## [1.1.4][]
#### Changed #### Changed
- Refactoring of `manpage()` to hide 'Options' and/or 'Arguments' if there are none to show. - Refactoring of `manpage()` to hide 'Options' and/or 'Arguments' if there are none to show
## [1.1.3][] ## [1.1.3][]
#### Changed #### Changed
- Updated `manpage()` to include `foregroundColour`, `headingColour`, and `additional` arguments. Removed `example` argument in favour of including it inside `additional`. - Updated `manpage()` to include `foregroundColour`, `headingColour`, and `additional` arguments. Removed `example` argument in favour of including it inside `additional`
- Added `pointybeard/helpers-cli-colour` composer package - Added `pointybeard/helpers-cli-colour` composer package
## [1.1.2][] ## [1.1.2][]
@ -38,8 +66,11 @@ This project adheres to [Semantic Versioning](http://semver.org/).
#### Added #### Added
- Initial release - Initial release
[Unreleased]: https://github.com/pointybeard/helpers-functions-cli/compare/1.1.5...integration [1.1.9]: https://github.com/pointybeard/helpers-functions-cli/compare/1.1.8...1.1.9
[1.1.5]: https://github.com/pointybeard/helpers-functions-cli/compare/1.1.5...1.1.5 [1.1.8]: https://github.com/pointybeard/helpers-functions-cli/compare/1.1.7...1.1.8
[1.1.7]: https://github.com/pointybeard/helpers-functions-cli/compare/1.1.6...1.1.7
[1.1.6]: https://github.com/pointybeard/helpers-functions-cli/compare/1.1.5...1.1.6
[1.1.5]: https://github.com/pointybeard/helpers-functions-cli/compare/1.1.4...1.1.5
[1.1.4]: https://github.com/pointybeard/helpers-functions-cli/compare/1.1.3...1.1.4 [1.1.4]: https://github.com/pointybeard/helpers-functions-cli/compare/1.1.3...1.1.4
[1.1.3]: https://github.com/pointybeard/helpers-functions-cli/compare/1.1.2...1.1.3 [1.1.3]: https://github.com/pointybeard/helpers-functions-cli/compare/1.1.2...1.1.3
[1.1.2]: https://github.com/pointybeard/helpers-functions-cli/compare/1.1.1...1.1.2 [1.1.2]: https://github.com/pointybeard/helpers-functions-cli/compare/1.1.1...1.1.2

View file

@ -3,7 +3,7 @@ unless otherwise specified, released under the MIT licence as follows:
----- begin license block ----- ----- begin license block -----
Copyright 2019 Alannah Kearney Copyright 2019-2021 Alannah Kearney
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

View file

@ -1,7 +1,7 @@
# PHP Helpers: Command-line Functions # PHP Helpers: Command-line Functions
- Version: v1.1.5 - Version: v1.1.10
- Date: May 24 2019 - Date: July 29 2021
- [Release notes](https://github.com/pointybeard/helpers-functions-cli/blob/master/CHANGELOG.md) - [Release notes](https://github.com/pointybeard/helpers-functions-cli/blob/master/CHANGELOG.md)
- [GitHub repository](https://github.com/pointybeard/helpers-functions-cli) - [GitHub repository](https://github.com/pointybeard/helpers-functions-cli)
@ -28,11 +28,15 @@ This library is a collection convenience function for command-line tasks. They a
The following functions are provided: The following functions are provided:
- `can_invoke_bash() : bool` - `run_command()`
- `is_su() : bool` - `which()`
- `usage(string $name, Cli\Input\InputCollection $collection) : string` - `can_invoke_bash()`
- `manpage(string $name, string $version, string $description, Input\InputCollection $collection, $foregroundColour=Colour\Colour::FG_DEFAULT, $headingColour=Colour\Colour::FG_WHITE, array $additional=[]): string` - `is_su()`
- `get_window_size(): array` - `run_command()`
- `usage()`
- `manpage()`
- `get_window_size()`
- `display_error_and_exit()`
Example usage: Example usage:
@ -46,6 +50,9 @@ use pointybeard\Helpers\Cli\Input;
use pointybeard\Helpers\Cli\Colour\Colour; use pointybeard\Helpers\Cli\Colour\Colour;
use pointybeard\Helpers\Functions\Cli; use pointybeard\Helpers\Functions\Cli;
var_dump(Cli\which("ls"));
// string(11) "/usr/bin/ls"
var_dump(Cli\can_invoke_bash()); var_dump(Cli\can_invoke_bash());
// bool(true) // bool(true)
@ -58,18 +65,31 @@ var_dump(Cli\get_window_size());
// 'lines' => string(2) "68" // 'lines' => string(2) "68"
// } // }
Cli\run_command("date", $out);
var_dump($out);
// string(29) "Mon 6 Apr 17:20:29 AEST 2020"
try{
Cli\run_command("not -a --command", $out, $err);
} catch(Cli\Exceptions\RunCommandFailedException $ex) {
var_dump($ex->getMessage(), $ex->getCommand(), $ex->getError());
}
// string(54) "Failed to run command. Returned: sh: 1: not: not found"
// string(16) "not -a --command"
// string(21) "sh: 1: not: not found"
echo Cli\manpage( echo Cli\manpage(
'test', 'test',
'1.0.1', '1.0.2',
'A simple test command with a really long description. This is an intentionally very long argument description so we can check that word wrapping is working correctly. It should wrap to the window', 'A simple test command with a really long description. This is an intentionally very long argument description so we can check that word wrapping is working correctly. It should wrap to the window',
(new Input\InputCollection()) (new Input\InputCollection())
->append( ->add(
Input\InputTypeFactory::build('Argument') Input\InputTypeFactory::build('Argument')
->name('action') ->name('action')
->flags(Input\AbstractInputType::FLAG_REQUIRED) ->flags(Input\AbstractInputType::FLAG_REQUIRED)
->description('The name of the action to perform. This is an intentionally very long argument description so we can check that word wrapping is working correctly') ->description('The name of the action to perform. This is an intentionally very long argument description so we can check that word wrapping is working correctly')
) )
->append( ->add(
Input\InputTypeFactory::build('IncrementingFlag') Input\InputTypeFactory::build('IncrementingFlag')
->name('v') ->name('v')
->flags(Input\AbstractInputType::FLAG_OPTIONAL | Input\AbstractInputType::FLAG_TYPE_INCREMENTING) ->flags(Input\AbstractInputType::FLAG_OPTIONAL | Input\AbstractInputType::FLAG_TYPE_INCREMENTING)
@ -77,18 +97,18 @@ echo Cli\manpage(
->validator(new Input\Validator( ->validator(new Input\Validator(
function (Input\AbstractInputType $input, Input\AbstractInputHandler $context) { function (Input\AbstractInputType $input, Input\AbstractInputHandler $context) {
// Make sure verbosity level never goes above 3 // Make sure verbosity level never goes above 3
return min(3, (int)$context->find('v')); return min(3, (int) $context->find('v'));
} }
)) ))
) )
->append( ->add(
Input\InputTypeFactory::build('Option') Input\InputTypeFactory::build('Option')
->name('P') ->name('P')
->flags(Input\AbstractInputType::FLAG_OPTIONAL | Input\AbstractInputType::FLAG_VALUE_OPTIONAL) ->flags(Input\AbstractInputType::FLAG_OPTIONAL | Input\AbstractInputType::FLAG_VALUE_OPTIONAL)
->description('Port to use for all connections.') ->description('Port to use for all connections.')
->default('3306') ->default('3306')
) )
->append( ->add(
Input\InputTypeFactory::build('LongOption') Input\InputTypeFactory::build('LongOption')
->name('data') ->name('data')
->short('d') ->short('d')
@ -102,29 +122,32 @@ echo Cli\manpage(
] ]
).PHP_EOL; ).PHP_EOL;
// test 1.0.0, A simple test command with a really long description. This is an intentionally very long argument description so we can check that word wrapping is working correctly. It should wrap to the window // test 1.0.2, A simple test command with a really long description. This is an intentionally very long argument description so we can check that word wrapping is working correctly. It should wrap to the window
// Usage: test [OPTIONS]... ACTION... // Usage: test [OPTIONS]... ACTION...
// //
// Arguments: // Arguments:
// ACTION The name of the action to perform. This is an // ACTION The name of the action to perform. This is an intentionally very
// intentionally very long argument description so we can check // long argument description so we can check that word wrapping is
// that word wrapping is working correctly // working correctly
// //
// Options: // Options:
// -v verbosity level. -v (errors only), -vv // -v verbosity level. -v (errors only), -vv (warnings and errors),
// (warnings and errors), -vvv (everything). // -vvv (everything).
// -P Port to use for all connections. // -P Port to use for all connections.
// -d, --data=VALUE Path to the input JSON data. // -d, --data=VALUE Path to the input JSON data.
// //
// Examples: // Examples:
// php -f test.php -- import -vvv -d test.json // php -f test.php -- import -vvv -d test.json
Cli\display_error_and_exit('Looks like something went wrong!', 'Fatal Error');
// Fatal Error
// Looks like something went wrong!
``` ```
## Support ## Support
If you believe you have found a bug, please report it using the [GitHub issue tracker](https://github.com/pointybeard/helpers-functions-cli/issues), If you believe you have found a bug, please report it using the [GitHub issue tracker](https://github.com/pointybeard/helpers-functions-cli/issues), or better yet, fork the library and submit a pull request.
or better yet, fork the library and submit a pull request.
## Contributing ## Contributing

View file

@ -1,9 +1,13 @@
{ {
"name": "pointybeard/helpers-functions-cli", "name": "pointybeard/helpers-functions-cli",
"version": "1.1.5",
"description": "A collection of functions relating to the command-line", "description": "A collection of functions relating to the command-line",
"homepage": "https://github.com/pointybeard/helpers-functions-cli", "homepage": "https://github.com/pointybeard/helpers-functions-cli",
"license": "MIT", "license": "MIT",
"minimum-stability": "stable",
"support": {
"issues": "https://github.com/pointybeard/helpers-functions-cli/issues",
"wiki": "https://github.com/pointybeard/helpers-functions-cli/wiki"
},
"authors": [ "authors": [
{ {
"name": "Alannah Kearney", "name": "Alannah Kearney",
@ -14,22 +18,34 @@
], ],
"require": { "require": {
"php": ">=7.2", "php": ">=7.2",
"pointybeard/helpers-cli-input": "~1.1", "pointybeard/helpers-cli-input": "~1.2.0",
"pointybeard/helpers-cli-colour": "~1", "pointybeard/helpers-cli-colour": "~1.0",
"pointybeard/helpers-functions-strings": "~1.1", "pointybeard/helpers-functions-strings": "~1.1",
"pointybeard/helpers-functions-flags": "~1" "pointybeard/helpers-functions-flags": "~1.0",
"pointybeard/helpers-functions-arrays": "~1.0",
"pointybeard/helpers-functions-debug": "~1.0",
"pointybeard/helpers-exceptions-readabletrace": "~1.0.0"
}, },
"require-dev": { "require-dev": {
"phpunit/phpunit": "^8" "friendsofphp/php-cs-fixer": "^3.0",
"squizlabs/php_codesniffer": "^3.0",
"damianopetrungaro/php-commitizen": "^0.1.0",
"php-parallel-lint/php-parallel-lint": "^1.0",
"php-parallel-lint/php-console-highlighter": "^0.5.0"
}, },
"support": {
"issues": "https://github.com/pointybeard/helpers-functions-cli/issues",
"wiki": "https://github.com/pointybeard/helpers-functions-cli/wiki"
},
"minimum-stability": "stable",
"autoload": { "autoload": {
"psr-4": {
"pointybeard\\Helpers\\Functions\\": "src/"
},
"files": [ "files": [
"src/Cli/Cli.php" "src/Cli/Cli.php"
] ]
},
"scripts": {
"tidy": "php-cs-fixer fix -v --using-cache=no",
"tidyDry": "@tidy --dry-run",
"test": [
"parallel-lint . --exclude vendor"
]
} }
} }

View file

@ -2,13 +2,117 @@
declare(strict_types=1); declare(strict_types=1);
/*
* This file is part of the "PHP Helpers: Command-line Functions" repository.
*
* Copyright 2019-2021 Alannah Kearney <hi@alannahkearney.com>
*
* For the full copyright and license information, please view the LICENCE
* file that was distributed with this source code.
*/
namespace pointybeard\Helpers\Functions\Cli; namespace pointybeard\Helpers\Functions\Cli;
use pointybeard\Helpers\Cli\Input; use Exception;
use pointybeard\Helpers\Cli\Colour; use pointybeard\Helpers\Cli\Colour;
use pointybeard\Helpers\Cli\Input;
use pointybeard\Helpers\Functions\Arrays;
use pointybeard\Helpers\Functions\Debug;
use pointybeard\Helpers\Functions\Flags; use pointybeard\Helpers\Functions\Flags;
use pointybeard\Helpers\Functions\Strings; use pointybeard\Helpers\Functions\Strings;
/*
* Uses proc_open() to run a command on the shell. Output and errors are captured
* and returned. If the command "fails" to run (i.e. return code is != 0), this
* function will throw an exception.
*
* Note that some commands will return a non-zero status code to signify, for
* example, no results found. This function is unable to tell the difference and
* will trigger an exception regardless. In this instance, It is advised to trap
* that exception and inspect both $stderr and $stdout to decide if it was
* actually due to failed command execution.
*
* @param string $command the full bash command to run
* @param string $stdout (optional) reference to capture output from STDOUT
* @param string $stderr (optional) reference to capture output from STDERR
* #param string $exitCode (options) reference to capture the command exit code
*
* @throws RunCommandFailedException
*/
if (!function_exists(__NAMESPACE__.'\run_command')) {
function run_command(string $command, string &$stdout = null, string &$stderr = null, int &$exitCode = null): void
{
$pipes = null;
$exitCode = null;
$proc = proc_open(
"{$command};echo $? >&3",
[
0 => ['pipe', 'r'], // STDIN
1 => ['pipe', 'w'], // STDOUT
2 => ['pipe', 'w'], // STDERR
3 => ['pipe', 'w'], // Used to capture the exit code
],
$pipes,
getcwd(),
null
);
// Close STDIN stream
fclose($pipes[0]);
// (guard) proc_open failed to return a resource
if (false == is_resource($proc)) {
throw new Exceptions\RunCommandFailedException($command, 'proc_open() returned FALSE.');
}
// Get contents of STDOUT and close stream
$stdout = trim(stream_get_contents($pipes[1]));
fclose($pipes[1]);
// Get contents od STDERR and close stream
$stderr = trim(stream_get_contents($pipes[2]));
fclose($pipes[2]);
// Grab the exit code then close the stream
if (false == feof($pipes[3])) {
$exitCode = (int) trim(stream_get_contents($pipes[3]));
}
fclose($pipes[3]);
// Close the process we created
proc_close($proc);
// (guard) proc_close return indiciated a failure
if (0 != $exitCode) {
// There was some kind of error. Throw an exception.
// If STDERR is empty, in effort to give back something
// meaningful, grab contents of STDOUT instead
throw new Exceptions\RunCommandFailedException($command, true == empty(trim($stderr)) ? $stdout : $stderr);
}
}
}
/*
* Returns the pathname for a specified command (or null if it cannot be found)
*
* @params $command the name of the command to look for
*
* @returns string|null
*/
if (!function_exists(__NAMESPACE__.'\which')) {
function which(string $command): ?string
{
try {
run_command("which {$command}", $output);
} catch (Exception $ex) {
$output = null;
}
return $output;
}
}
/* /*
* Checks if bash can be invoked. * Checks if bash can be invoked.
* *
@ -38,31 +142,7 @@ if (!function_exists(__NAMESPACE__.'is_su')) {
} }
} }
if (!function_exists(__NAMESPACE__.'usage')) { /*
function usage(string $name, Input\InputCollection $collection): string
{
$arguments = [];
foreach ($collection->getItemsByType('Argument') as $a) {
$arguments[] = strtoupper(
// Wrap with square brackets if it's not required
Flags\is_flag_set(Input\AbstractInputType::FLAG_OPTIONAL, $a->flags()) ||
!Flags\is_flag_set(Input\AbstractInputType::FLAG_REQUIRED, $a->flags())
? "[{$a->name()}]"
: $a->name()
);
}
$arguments = trim(implode($arguments, ' '));
return sprintf(
'Usage: %s [OPTIONS]... %s%s',
$name,
$arguments,
strlen($arguments) > 0 ? '...' : ''
);
}
}
/**
* Uses tput to find out the size of the window (columns and lines) * Uses tput to find out the size of the window (columns and lines)
* @return array an array containing exactly 2 items: 'cols' and 'lines' * @return array an array containing exactly 2 items: 'cols' and 'lines'
*/ */
@ -76,16 +156,40 @@ if (!function_exists(__NAMESPACE__.'get_window_size')) {
} }
} }
if (!function_exists(__NAMESPACE__.'usage')) {
function usage(string $name, Input\InputCollection $collection): string
{
$arguments = [];
foreach ($collection->getItemsByType('Argument') as $a) {
$arguments[] = strtoupper(
// Wrap with square brackets if it's not required
Flags\is_flag_set(Input\AbstractInputType::FLAG_OPTIONAL, $a->flags()) ||
!Flags\is_flag_set(Input\AbstractInputType::FLAG_REQUIRED, $a->flags())
? "[{$a->name()}]"
: $a->name()
);
}
$arguments = trim(implode(' ', $arguments));
return sprintf(
'Usage: %s [OPTIONS]... %s%s',
$name,
$arguments,
strlen($arguments) > 0 ? '...' : ''
);
}
}
if (!function_exists(__NAMESPACE__.'manpage')) { if (!function_exists(__NAMESPACE__.'manpage')) {
function manpage(string $name, string $version, string $description, Input\InputCollection $collection, $foregroundColour=Colour\Colour::FG_DEFAULT, $headingColour=Colour\Colour::FG_WHITE, array $additionalSections=[]): string function manpage(string $name, string $version, string $description, Input\InputCollection $collection, $foregroundColour = Colour\Colour::FG_DEFAULT, $headingColour = Colour\Colour::FG_WHITE, array $additionalSections = []): string
{ {
// Convienence function for wrapping a heading with colour // Convienence function for wrapping a heading with colour
$heading = function(string $input) use ($headingColour) { $heading = function (string $input) use ($headingColour) {
return Colour\Colour::colourise($input, $headingColour); return Colour\Colour::colourise($input, $headingColour);
}; };
// Convienence function for wrapping input in a specified colour // Convienence function for wrapping input in a specified colour
$colourise = function(string $input) use ($foregroundColour) { $colourise = function (string $input) use ($foregroundColour) {
return Colour\Colour::colourise($input, $foregroundColour); return Colour\Colour::colourise($input, $foregroundColour);
}; };
@ -102,37 +206,109 @@ if (!function_exists(__NAMESPACE__.'manpage')) {
$arguments = []; $arguments = [];
$options = []; $options = [];
foreach ($collection->getItemsByType("Argument") as $a) { foreach ($collection->getItemsByType('Argument') as $a) {
$arguments[] = (string) $a; $arguments[] = (string) $a;
} }
foreach($collection->getTypes() as $type) { foreach ($collection->getItemsExcludeByType('Argument') as $o) {
if($type == 'Argument') {
continue;
}
foreach ($collection->getItemsByType($type) as $o) {
$options[] = (string) $o; $options[] = (string) $o;
} }
}
// Add the arguments, if there are any. // Add the arguments, if there are any.
if(false === empty($arguments)){ if (false === empty($arguments)) {
$sections[] = $heading("Arguments:"); $sections[] = $heading('Arguments:');
$sections[] = $colourise(implode($arguments, PHP_EOL)) . PHP_EOL; $sections[] = $colourise(implode(PHP_EOL, $arguments)).PHP_EOL;
} }
// Add the options, if there are any. // Add the options, if there are any.
if(false === empty($options)){ if (false === empty($options)) {
$sections[] = $heading("Options:"); $sections[] = $heading('Options:');
$sections[] = $colourise(implode($options, PHP_EOL)) . PHP_EOL; $sections[] = $colourise(implode(PHP_EOL, $options)).PHP_EOL;
} }
// Iterate over all additional items and add them as new sections // Iterate over all additional items and add them as new sections
foreach($additionalSections as $name => $contents) { foreach ($additionalSections as $name => $contents) {
$sections[] = $heading("{$name}:"); $sections[] = $heading("{$name}:");
$sections[] = $colourise($contents) . PHP_EOL; $sections[] = $colourise($contents).PHP_EOL;
} }
return implode($sections, PHP_EOL); return implode(PHP_EOL, $sections);
}
}
if (!function_exists(__NAMESPACE__."\display_error_and_exit")) {
function display_error_and_exit($message, $heading = 'Error', $background = Colour\Colour::BG_RED, ?array $trace = null): void
{
$padCharacter = ' ';
$paddingBufferSize = 0.15; // 15%
$minimumWindowWidth = 40;
$edgePaddingLength = 5;
$edgePadding = str_repeat($padCharacter, $edgePaddingLength);
// Convenience function for adding the background to a line.
$add_background = function (string $string, bool $bold = false) use ($edgePadding, $background): string {
$string = $edgePadding.$string.$edgePadding;
return Colour\Colour::colourise(
$string,
(
true == $bold
? Colour\Colour::FG_WHITE
: Colour\Colour::FG_DEFAULT
),
$background
);
};
// Get the window dimensions but restrict width to minimum
// of $minimumWindowWidth
$window = get_window_size();
$window['cols'] = max($minimumWindowWidth, $window['cols']);
// This shrinks the total line length (derived by the window width) by
// $paddingBufferSize
$paddingBuffer = (int) ceil($window['cols'] * $paddingBufferSize);
$lineLength = $window['cols'] - (2 * $edgePaddingLength) - $paddingBuffer;
$emptyLine = $add_background(str_repeat($padCharacter, $lineLength), true);
$heading = Strings\mb_str_pad(trim($heading), $lineLength, $padCharacter, \STR_PAD_RIGHT);
$message = Strings\utf8_wordwrap_array($message, $lineLength, PHP_EOL, true);
// Remove surrounding whitespace
$message = array_map('trim', $message);
// Remove empty elements from the array
$message = Arrays\array_remove_empty($message);
// Reset array indicies
$message = array_values($message);
// Wrap everything in red
for ($ii = 0; $ii < count($message); ++$ii) {
$message[$ii] = $add_background(Strings\mb_str_pad(
$message[$ii],
mb_strlen($heading),
$padCharacter,
\STR_PAD_RIGHT
));
}
// Add an empty red line at the end
array_push($message, $emptyLine);
// Print the error message, starting with an empty red line
printf(
"\r\n%s\r\n%s\r\n%s\r\n%s",
$emptyLine,
$add_background($heading, true),
implode(PHP_EOL, $message),
!empty($trace) && count($trace) > 0
? PHP_EOL.sprintf("Trace\r\n==========\r\n%s\r\n", Debug\readable_debug_backtrace($trace))
: ''
);
exit(1);
} }
} }

View file

@ -0,0 +1,40 @@
<?php
declare(strict_types=1);
/*
* This file is part of the "PHP Helpers: Command-line Functions" repository.
*
* Copyright 2019-2021 Alannah Kearney <hi@alannahkearney.com>
*
* For the full copyright and license information, please view the LICENCE
* file that was distributed with this source code.
*/
namespace pointybeard\Helpers\Functions\Cli\Exceptions;
use pointybeard\Helpers\Exceptions\ReadableTrace;
class RunCommandFailedException extends ReadableTrace\ReadableTraceException
{
private $command;
private $error;
public function __construct(string $command, string $error, int $code = 0, \Exception $previous = null)
{
$this->command = $command;
$this->error = $error;
parent::__construct("Failed to run command. Returned: {$error}", $code, $previous);
}
public function getCommand(): string
{
return $this->command;
}
public function getError(): string
{
return $this->error;
}
}