Compare commits

...

12 commits

9 changed files with 339 additions and 49 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,24 @@
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][] ## [1.1.7][]
#### Added #### Added
@ -22,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][]
@ -49,7 +66,9 @@ 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.6...integration [1.1.9]: https://github.com/pointybeard/helpers-functions-cli/compare/1.1.8...1.1.9
[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.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.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

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.7 - Version: v1.1.10
- Date: May 26 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,8 +28,11 @@ This library is a collection convenience function for command-line tasks. They a
The following functions are provided: The following functions are provided:
- `run_command()`
- `which()`
- `can_invoke_bash()` - `can_invoke_bash()`
- `is_su()` - `is_su()`
- `run_command()`
- `usage()` - `usage()`
- `manpage()` - `manpage()`
- `get_window_size()` - `get_window_size()`
@ -47,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)
@ -59,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)
@ -82,14 +101,14 @@ echo Cli\manpage(
} }
)) ))
) )
->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')
@ -103,17 +122,17 @@ 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.
// //
@ -128,8 +147,7 @@ Cli\display_error_and_exit('Looks like something went wrong!', 'Fatal Error');
## 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.7",
"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,24 +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", "pointybeard/helpers-functions-arrays": "~1.0",
"pointybeard/helpers-functions-debug": "~1" "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,14 +2,116 @@
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\Functions\Flags; use pointybeard\Helpers\Cli\Input;
use pointybeard\Helpers\Functions\Strings;
use pointybeard\Helpers\Functions\Arrays; use pointybeard\Helpers\Functions\Arrays;
use pointybeard\Helpers\Functions\Debug; use pointybeard\Helpers\Functions\Debug;
use pointybeard\Helpers\Functions\Flags;
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.
@ -67,7 +169,7 @@ if (!function_exists(__NAMESPACE__.'usage')) {
: $a->name() : $a->name()
); );
} }
$arguments = trim(implode($arguments, ' ')); $arguments = trim(implode(' ', $arguments));
return sprintf( return sprintf(
'Usage: %s [OPTIONS]... %s%s', 'Usage: %s [OPTIONS]... %s%s',
@ -108,25 +210,20 @@ if (!function_exists(__NAMESPACE__.'manpage')) {
$arguments[] = (string) $a; $arguments[] = (string) $a;
} }
foreach ($collection->getTypes() as $type) { foreach ($collection->getItemsExcludeByType('Argument') as $o) {
if ('Argument' == $type) {
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
@ -135,7 +232,7 @@ if (!function_exists(__NAMESPACE__.'manpage')) {
$sections[] = $colourise($contents).PHP_EOL; $sections[] = $colourise($contents).PHP_EOL;
} }
return implode($sections, PHP_EOL); return implode(PHP_EOL, $sections);
} }
} }
@ -149,7 +246,7 @@ if (!function_exists(__NAMESPACE__."\display_error_and_exit")) {
$edgePadding = str_repeat($padCharacter, $edgePaddingLength); $edgePadding = str_repeat($padCharacter, $edgePaddingLength);
// Convenience function for adding the background to a line. // Convenience function for adding the background to a line.
$add_background = function (string $string, bool $bold = false) use ($padCharacter, $edgePadding, $background): string { $add_background = function (string $string, bool $bold = false) use ($edgePadding, $background): string {
$string = $edgePadding.$string.$edgePadding; $string = $edgePadding.$string.$edgePadding;
return Colour\Colour::colourise( return Colour\Colour::colourise(
@ -206,9 +303,9 @@ if (!function_exists(__NAMESPACE__."\display_error_and_exit")) {
"\r\n%s\r\n%s\r\n%s\r\n%s", "\r\n%s\r\n%s\r\n%s\r\n%s",
$emptyLine, $emptyLine,
$add_background($heading, true), $add_background($heading, true),
implode($message, PHP_EOL), implode(PHP_EOL, $message),
!empty($trace) && count($trace) > 0 !empty($trace) && count($trace) > 0
? PHP_EOL . sprintf("Trace\r\n==========\r\n%s\r\n", Debug\readable_debug_backtrace($trace)) ? PHP_EOL.sprintf("Trace\r\n==========\r\n%s\r\n", Debug\readable_debug_backtrace($trace))
: '' : ''
); );

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