• Stars
    star
    333
  • Rank 126,599 (Top 3 %)
  • Language
    PHP
  • License
    MIT License
  • Created over 6 years ago
  • Updated 8 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

PHP Console Application made easy- build great console apps with ease. Comes with Zero Dependency and Autocompletion support. Think of it as a PHP cli application framework.

adhocore/cli

Framework agnostic Command Line Interface utilities and helpers for PHP. Build Console App with ease, fun and love.

Latest Version Build Scrutinizer CI Codecov branch StyleCI Software License Tweet Support

  • Command line application made easy
  • Inspired by nodejs commander (thanks tj)
  • Zero dependency.
  • For PHP7, PHP8 and for good

Screen Preview

What's included

Core: Argv parser · Cli application · Shell

IO: Colorizer · Cursor manipulator · Progress bar · Stream writer · Stream reader

Other: Autocompletion

Installation

# PHP8.0 and above v1.0.0
composer require adhocore/cli:^v1.0.0

# PHP 7.x
composer require adhocore/cli:^v0.9.0

Usage

Argv parser

$command = new Ahc\Cli\Input\Command('rmdir', 'Remove dirs');

$command
    ->version('0.0.1-dev')
    // Arguments are separated by space
    // Format: `<name>` for required, `[name]` for optional
    //  `[name:default]` for default value, `[name...]` for variadic (last argument)
    ->arguments('<dir> [dirs...]')
    // `-h --help`, `-V --version`, `-v --verbosity` options are already added by default.
    // Format: `<name>` for required, `[name]` for optional
    ->option('-s --with-subdir', 'Also delete subdirs (`with` means false by default)')
    ->option('-e,--no-empty', 'Delete empty (`no` means true by default)')
    // Specify santitizer/callback as 3rd param, default value as 4th param
    ->option('-d|--depth [nestlevel]', 'How deep to process subdirs', 'intval', 5)
    ->parse(['thisfile.php', '-sev', 'dir', 'dir1', 'dir2', '-vv']) // `$_SERVER['argv']`
;

// Print all values:
print_r($command->values());

/*Array
(
    [help] =>
    [version] => 0.0.1
    [verbosity] => 3
    [dir] => dir
    [dirs] => Array
        (
            [0] => dir1
            [1] => dir2
        )

    [subdir] => true
    [empty] => false
    [depth] => 5
)*/

// To get values for options except the default ones (help, version, verbosity)
print_r($command->values(false));

// Pick a value by name
$command->dir;   // dir
$command->dirs;  // [dir1, dir2]
$command->depth; // 5

Command help

It can be triggered manually with $command->showHelp() or automatic when -h or --help option is passed to $command->parse().

For above example, the output would be: Command Help

Command version

It can be triggered manually with $command->showVersion() or automatic when -V or --version option is passed to $command->parse().

For above example, the output would be:

0.0.1-dev

Console app

Definitely check adhocore/phint - a real world console application made using adhocore/cli.

Here we simulate a git app with limited functionality of add, and checkout. You will see how intuitive, fluent and cheese building a console app is!

Git app

$app = new Ahc\Cli\Application('git', '0.0.1');

$app
    // Register `add` command
    ->command('add', 'Stage changed files', 'a') // alias a
        // Set options and arguments for this command
        ->arguments('<path> [paths...]')
        ->option('-f --force', 'Force add ignored file', 'boolval', false)
        ->option('-N --intent-to-add', 'Add content later but index now', 'boolval', false)
        // Handler for this command: param names should match but order can be anything :)
        ->action(function ($path, $paths, $force, $intentToAdd) {
            array_unshift($paths, $path);

            echo ($intentToAdd ? 'Intent to add ' : 'Add ')
                . implode(', ', $paths)
                . ($force ? ' with force' : '');

            // If you return integer from here, that will be taken as exit error code
        })
        // Done setting up this command for now, tap() to retreat back so we can add another command
        ->tap()
    ->command('checkout', 'Switch branches', 'co') // alias co
        ->arguments('<branch>')
        ->option('-b --new-branch', 'Create a new branch and switch to it', false)
        ->option('-f --force', 'Checkout even if index differs', 'boolval', false)
        ->action(function ($branch, $newBranch, $force) {
            echo 'Checkout to '
                . ($newBranch ? 'new ' . $branch : $branch)
                . ($force ? ' with force' : '');
        })
;

// Parse only parses input but doesnt invoke action
$app->parse(['git', 'add', 'path1', 'path2', 'path3', '-f']);

// Handle will do both parse and invoke action.
$app->handle(['git', 'add', 'path1', 'path2', 'path3', '-f']);
// Will produce: Add path1, path2, path3 with force

$app->handle(['git', 'co', '-b', 'master-2', '-f']);
// Will produce: Checkout to new master-2 with force

Organized app

Instead of inline commands/actions, we define and add our own commands (having interact() and execute()) to the app:

class InitCommand extends Ahc\Cli\Input\Command
{
    public function __construct()
    {
        parent::__construct('init', 'Init something');

        $this
            ->argument('<arrg>', 'The Arrg')
            ->argument('[arg2]', 'The Arg2')
            ->option('-a --apple', 'The Apple')
            ->option('-b --ball', 'The ball')
            // Usage examples:
            ->usage(
                // append details or explanation of given example with ` ## ` so they will be uniformly aligned when shown
                '<bold>  init</end> <comment>--apple applet --ball ballon <arggg></end> ## details 1<eol/>' .
                // $0 will be interpolated to actual command name
                '<bold>  $0</end> <comment>-a applet -b ballon <arggg> [arg2]</end> ## details 2<eol/>'
            );
    }

    // This method is auto called before `self::execute()` and receives `Interactor $io` instance
    public function interact(Ahc\Cli\IO\Interactor $io)
    {
        // Collect missing opts/args
        if (!$this->apple) {
            $this->set('apple', $io->prompt('Enter apple'));
        }

        if (!$this->ball) {
            $this->set('ball', $io->prompt('Enter ball'));
        }

        // ...
    }

    // When app->handle() locates `init` command it automatically calls `execute()`
    // with correct $ball and $apple values
    public function execute($ball, $apple)
    {
        $io = $this->app()->io();

        $io->write('Apple ' . $apple, true);
        $io->write('Ball ' . $ball, true);

        // more codes ...

        // If you return integer from here, that will be taken as exit error code
    }
}

class OtherCommand extends Ahc\Cli\Input\Command
{
    // ...
}

// Init App with name and version
$app = new Ahc\Cli\Application('App', 'v0.0.1');

// Add commands with optional aliases`
$app->add(new InitCommand, 'i');
$app->add(new OtherCommand, 'o');

// Set logo
$app->logo('Ascii art logo of your app');

$app->handle($_SERVER['argv']); // if argv[1] is `i` or `init` it executes InitCommand

Grouping commands

Grouped commands are listed together in commands list. Explicit grouping a command is optional. By default if a command name has a colon : then the part before it is taken as a group, else * is taken as a group.

Example: command name app:env has a default group app, command name appenv has group *.

// Add grouped commands:
$app->group('Configuration', function ($app) {
    $app->add(new ConfigSetCommand);
    $app->add(new ConfigListCommand);
});

// Alternatively, set group one by one in each commands:
$app->add((new ConfigSetCommand)->inGroup('Config'));
$app->add((new ConfigListCommand)->inGroup('Config'));
...

Exception handler

Set a custom exception handler as callback. The callback receives exception & exit code. The callback may rethrow exception or may exit the program or just log exception and do nothing else.

$app = new Ahc\Cli\Application('App', 'v0.0.1');
$app->add(...);
$app->onException(function (Throwable $e, int $exitCode) {
    // send to sentry
    // write to logs

    // optionally, exit with exit code:
    exit($exitCode);

    // or optionally rethrow, a rethrown exception is propagated to top layer caller.
    throw $e;
})->handle($argv);

App help

It can be triggered manually with $app->showHelp() or automatic when -h or --help option is passed to $app->parse(). Note If you pass something like ['app', cmd', '-h'] to $app->parse() it will automatically and instantly show you help of that cmd and not the $app.

For above example, the output would be: App Help

App version

Same version number is passed to all attached Commands. So you can trigger version on any of the commands.

Shell

Very thin shell wrapper that provides convenience methods around proc_open().

Basic usage

$shell = new Ahc\Cli\Helper\Shell($command = 'php -v', $rawInput = null);

// Waits until proc finishes
$shell->execute($async = false); // default false

echo $shell->getOutput(); // PHP version string (often with zend/opcache info)

Advanced usage

$shell = new Ahc\Cli\Helper\Shell('php /some/long/running/scipt.php');

// With async flag, doesnt wait for proc to finish!
$shell->setOptions($workDir = '/home', $envVars = [])
    ->execute($async = true)
    ->isRunning(); // true

// Force stop anytime (please check php.net/proc_close)
$shell->stop(); // also closes pipes

// Force kill anytime (please check php.net/proc_terminate)
$shell->kill();

Timeout

$shell = new Ahc\Cli\Helper\Shell('php /some/long/running/scipt.php');

// Wait for at most 10.5 seconds for proc to finish!
// If it doesnt complete by then, throws exception
$shell->setOptions($workDir, $envVars, $timeout = 10.5)->execute();

// And if it completes within timeout, you can access the stdout/stderr
echo $shell->getOutput();
echo $shell->getErrorOutput();

Cli Interaction

You can perform user interaction like printing colored output, reading user input programatically and moving the cursors around with provided Ahc\Cli\IO\Interactor.

$interactor = new Ahc\Cli\IO\Interactor;

// For mocking io:
$interactor = new Ahc\Cli\IO\Interactor($inputPath, $outputPath);

Confirm

$confirm = $interactor->confirm('Are you happy?', 'n'); // Default: n (no)
$confirm // is a boolean
    ? $interactor->greenBold('You are happy :)', true)  // Output green bold text
    : $interactor->redBold('You are sad :(', true);     // Output red bold text

Single choice

$fruits = ['a' => 'apple', 'b' => 'banana'];
$choice = $interactor->choice('Select a fruit', $fruits, 'b');
$interactor->greenBold("You selected: {$fruits[$choice]}", true);

Multiple choices

$fruits  = ['a' => 'apple', 'b' => 'banana', 'c' => 'cherry'];
$choices = $interactor->choices('Select fruit(s)', $fruits, ['b', 'c']);
$choices = \array_map(function ($c) use ($fruits) { return $fruits[$c]; }, $choices);
$interactor->greenBold('You selected: ' . implode(', ', $choices), true);

Prompt free input

$any = $interactor->prompt('Anything', rand(1, 100)); // Random default
$interactor->greenBold("Anything is: $any", true);

Prompt with validation

$nameValidator = function ($value) {
    if (\strlen($value) < 5) {
        throw new \InvalidArgumentException('Name should be atleast 5 chars');
    }

    return $value;
};

// No default, Retry 5 more times
$name = $interactor->prompt('Name', null, $nameValidator, 5);
$interactor->greenBold("The name is: $name", true);

Prompt hidden

On windows platform, it may change the fontface which can be fixed.

$passValidator = function ($pass) {
    if (\strlen($pass) < 6) {
        throw new \InvalidArgumentException('Password too short');
    }

    return $pass;
};

$pass = $interactor->promptHidden('Password', $passValidator, 2);

Interactive Preview

IO Components

The interactor is composed of Ahc\Cli\Input\Reader and Ahc\Cli\Output\Writer while the Writer itself is composed of Ahc\Cli\Output\Color. All these components can be used standalone.

Color

Color looks cool!

$color = new Ahc\Cli\Output\Color;

Simple usage

echo $color->warn('This is warning');
echo $color->info('This is info');
echo $color->error('This is error');
echo $color->comment('This is comment');
echo $color->ok('This is ok msg');

Custom style

Ahc\Cli\Output\Color::style('mystyle', [
    'bg' => Ahc\Cli\Output\Color::CYAN,
    'fg' => Ahc\Cli\Output\Color::WHITE,
    'bold' => 1, // You can experiment with 0, 1, 2, 3 ... as well
]);

echo $color->mystyle('My text');

Cursor

Move cursor around, erase line up or down, clear screen.

$cursor = new Ahc\Cli\Output\Cursor;

echo  $cursor->up(1)
    . $cursor->down(2)
    . $cursor->right(3)
    . $cursor->left(4)
    . $cursor->next(0)
    . $cursor->prev(2);
    . $cursor->eraseLine()
    . $cursor->clear()
    . $cursor->clearUp()
    . $cursor->clearDown()
    . $cursor->moveTo(5, 8); // x, y

Progress Bar

Easily add a progress bar to your output:

$progress = new Ahc\Cli\Output\ProgressBar(100);
for ($i = 0; $i <= 100; $i++) {
    $progress->current($i);

    // Simulate something happening
    usleep(80000);
}

You can also manually advance the bar:

$progress = new Ahc\Cli\Output\ProgressBar(100);

// Do something

$progress->advance(); // Adds 1 to the current progress

// Do something

$progress->advance(10); // Adds 10 to the current progress

// Do something

$progress->advance(5, 'Still going.'); // Adds 5, displays a label

You can override the progress bar options to customize it to your liking:

$progress = new Ahc\Cli\Output\ProgressBar(100);
$progress->option('pointer', '>>');
$progress->option('loader', '▩');

// You can set the progress fluently
$progress->option('pointer', '>>')->option('loader', '▩');

// You can also use an associative array to set many options in one time
$progress->option([
    'pointer' => '>>',
    'loader'  => '▩'
]);

// Available options
+------------+------------------------------+---------------+
| Option     | Description                  | Default value |
+------------+------------------------------+---------------+
| pointer    | The progress bar head symbol | >             |
| loader     | The loader symbol            | =             |
| color      | The color of progress bar    | white         |
| labelColor | The text color of the label  | white         |
+------------+------------------------------+---------------+

Writer

Write anything in style.

$writer = new Ahc\Cli\Output\Writer;

// All writes are forwarded to STDOUT
// But if you specify error, then to STDERR
$writer->errorBold('This is error');

Output formatting

You can call methods composed of any combinations: '<colorName>', 'bold', 'bg', 'fg', 'warn', 'info', 'error', 'ok', 'comment' ... in any order (eg: bgRedFgBlaock, boldRed, greenBold, commentBgPurple and so on ...)

$writer->bold->green->write('It is bold green');
$writer->boldGreen('It is bold green'); // Same as above
$writer->comment('This is grayish comment', true); // True indicates append EOL character.
$writer->bgPurpleBold('This is white on purple background');

Free style

Many colors with one single call: wrap text with tags <method> and </end> For NL/EOL just use <eol> or </eol> or <eol/>.

Great for writing long colorful texts for example command usage info.

$writer->colors('<red>This is red</end><eol><bgGreen>This has bg Green</end>');

Raw output

$writer->raw('Enter name: ');

Tables

Just pass array of assoc arrays. The keys of first array will be taken as heading. Heading is auto inflected to human readable capitalized words (ucwords).

$writer->table([
    ['a' => 'apple', 'b-c' => 'ball', 'c_d' => 'cat'],
    ['a' => 'applet', 'b-c' => 'bee', 'c_d' => 'cute'],
]);

Gives something like:

+--------+------+------+
| A      | B C  | C D  |
+--------+------+------+
| apple  | ball | cat  |
| applet | bee  | cute |
+--------+------+------+

Designing table look and feel

Just pass 2nd param $styles:

$writer->table([
    ['a' => 'apple', 'b-c' => 'ball', 'c_d' => 'cat'],
    ['a' => 'applet', 'b-c' => 'bee', 'c_d' => 'cute'],
], [
    // for => styleName (anything that you would call in $writer instance)
    'head' => 'boldGreen', // For the table heading
    'odd'  => 'bold',      // For the odd rows (1st row is odd, then 3, 5 etc)
    'even' => 'comment',   // For the even rows (2nd row is even, then 4, 6 etc)
]);

// 'head', 'odd', 'even' are all the styles for now
// In future we may support styling a column by its name!

Reader

Read and pre process user input.

$reader = new Ahc\Cli\Input\Reader;

// No default, callback fn `ucwords()`
$reader->read(null, 'ucwords');

// Default 'abc', callback `trim()`
$reader->read('abc', 'trim');

// Read at most first 5 chars
// (if ENTER is pressed before 5 chars then further read is aborted)
$reader->read('', 'trim', 5);

// Read but dont echo back the input
$reader->readHidden($default, $callback);

// Read from piped stream (or STDIN) if available without waiting
$reader->readPiped();

// Pass in a callback for if STDIN is empty
// The callback recieves $reader instance and MUST return string
$reader->readPiped(function ($reader) {
    // Wait to read a line!
    return $reader->read();

    // Wait to read multi lines (until Ctrl+D pressed)
    return $reader->readAll();
});

Exceptions

Whenever an exception is caught by Application::handle(), it will show a beautiful stack trace and exit with non 0 status code.

Exception Preview

Autocompletion

Any console applications that are built on top of adhocore/cli can entertain autocomplete of commands and options in zsh shell with oh-my-zsh.

All you have to do is add one line to the end of ~/.oh-my-zsh/custom/plugins/ahccli/ahccli.plugin.zsh:

compdef _ahccli <appname>

Example: compdef _ahccli phint for phint.

That is cumbersome to perform manually, here's a complete command you can copy/paste/run:

One time setup

mkdir -p ~/.oh-my-zsh/custom/plugins/ahccli && cd ~/.oh-my-zsh/custom/plugins/ahccli

[ -f ./ahccli.plugin.zsh ] || curl -sSLo ./ahccli.plugin.zsh https://raw.githubusercontent.com/adhocore/php-cli/master/ahccli.plugin.zsh

chmod 760 ./ahccli.plugin.zsh && cd -
Load ahccli plugin

This is also one time setup.

# Open .zshrc
nano ~/.zshrc

# locate plugins=(... ...) and add ahccli
plugins=(git ... ... ahccli)

# ... then save it (Ctrl + O)

Registering app

# replace appname with real name eg: phint
echo compdef _ahccli appname >> ~/.oh-my-zsh/custom/plugins/ahccli/ahccli.plugin.zsh

Of course you can add multiple apps, just change appname in above command

Then either restart the shell or source the plugin like so:

source ~/.oh-my-zsh/custom/plugins/ahccli/ahccli.plugin.zsh

Trigger autocomplete

appname <tab>            # autocompletes commands               (phint <tab>)
appname subcommand <tab> # autocompletes options for subcommand (phint init <tab>)

Related

Contributors

License

© 2017-2020, Jitendra Adhikari | MIT

Credits

This project is release managed by please.

More Repositories

1

gronx

Lightweight, fast and dependency-free Cron expression parser (due checker, next/prev due date finder), task runner, job scheduler and/or daemon for Golang (tested on v1.13+) and standalone usage. If you are bold, use it to replace crontab entirely.
Go
317
star
2

php-jwt

Ultra lightweight, dependency free and standalone JSON web token (JWT) library for PHP5.6 to PHP8.2. This library makes JWT a cheese. It is a minimal JWT integration for PHP.
PHP
271
star
3

urlsh

Golang URL shortener and bookmarker service with UI, API, Cache, Hits Counter and forwarder using postgres and redis in backend, bulma in frontend. Think of it as self hosting ready url shortener.
Go
144
star
4

docker-phpfpm

Lightweight (~100mb) Docker PHP FPM for both arm and amd arch on alpine 3.17 with PHP8.0.30/8.1.25/8.2.12 (also 7.4.33) with ~82-84 useful extensions (you can disable not necessary ones easily)
Dockerfile
96
star
5

phint

Interactively scaffolds and init new (or fixup old) PHP project/library with sane defaults using templates in no time
PHP
93
star
6

please

please is semver release made easy, detects current version from API or tags and next version from commits, creates detailed changelogs that are configurable.
Shell
86
star
7

fast

Check your internet speed/bandwidth right from your terminal. Built on Golang using chromedp
Go
81
star
8

php-cron-expr

Ultra lightweight, Dependency free and Super Fast Cron Expression parser for PHP
PHP
51
star
9

htmlup

Light and fast markdown parser, that parses markdown in a way human does
PHP
50
star
10

php-json-fixer

Fix truncated JSON data
PHP
45
star
11

php-underscore

PHP underscore inspired &/or cloned from _.js, with extra goodies like higher order messaging
PHP
44
star
12

phalcon-ext

Foundations, adapters, extensions, middlewares and utilities for Phalcon v4
PHP
42
star
13

tusc.sh

tus 1.0.0 client protocol implementation for bash. Resumable large file upload to Tus sever from terminal using bash script
Shell
32
star
14

chin

A Go lang library to show a spinner as user waits for any long running jobs to finish.
Go
31
star
15

goic

Golang OpenID Connect Client
Go
28
star
16

php-json-comment

Lightweight JSON comment and trailing comma stripper library for PHP with support for literal newlines and nested JSON strings.
PHP
27
star
17

php-env

A small and fast .env loader for PHP
PHP
23
star
18

with

With provides object like fluent interface for scalars and non-objects
PHP
17
star
19

jquery-plugins

jQuery plugins: BS calendar & datepicker, auto form filler, css grid float top, lazy & ajax pagination
JavaScript
16
star
20

crx-jtrans

jTransliter - the roman to unicode transliter as Google chrome extension
JavaScript
15
star
21

dsa

data structure and algorithm - examples and implementations
PHP
15
star
22

stiky

modular backbonejs application for sticky notes
JavaScript
14
star
23

jsonc

Golang (v1.13+) JSON5 preprocessor supporting comments, trailing comma, unquoted key/single-quoted string, hex number, trailing decimal point, literal newlines and more.
Go
14
star
24

twig-yall

Resource lazy loader extension for twig using malchata/yall.js
PHP
12
star
25

crx-loshin

loshin, the Load Shedding Indicator
JavaScript
12
star
26

php-cli-syntax

PHP Code Syntax Highlighter and/or exporter for CLI. Zero Dependency.
PHP
11
star
27

vue-inventory

Vue JS inventory app for hotel
HTML
11
star
28

php-env-bench

Benchmarking env loaders/parsers for PHP.
PHP
11
star
29

fsb

f...ing simple benchmark(ing)
PHP
10
star
30

get-in

Handy Traversal of chained objects with error trap and default value (suited for View)
PHP
9
star
31

sublime-phpcsfixer

A simple and minimal plugin for sublime editor to fix php files with php-cs-fixer via context menu
Python
9
star
32

php-cron-bench

Benchmarking cron parsers for PHP.
PHP
9
star
33

asserts

More PHPUnit assertions as a Trait
PHP
9
star
34

log-viewer

Simple log viewer app built with Lumen framework and VueJS
PHP
9
star
35

php-polyfills

Miscellaneous polyfills for older PHP versions
PHP
8
star
36

win-cli-launcher

a command line launcher for windows os
Batchfile
8
star
37

py-routes

Python recursive shortest path algo to find the optimal route between two points in terms of number of stops and duration
Python
8
star
38

php-tools

php tools for string, numbers, dates etc
PHP
7
star
39

gh-sc

Cancel all but the last SC automatically
JavaScript
7
star
40

plastic

PHP elasticsearch wrapper designed to be minimal, intuitive and dependency free
PHP
7
star
41

ci-captcha

Easy, hassle free, plug and play Code Igniter captcha library [OLD CODE]
PHP
7
star
42

live-console

PHP live console in browser
PHP
6
star
43

crx-adblock

chrome extension to hide/block custom ads
JavaScript
6
star
44

crx-joom

auto zoom the webpage for more readability and less eye stress
JavaScript
6
star
45

adhocore.github.io

adhocore.github.io
HTML
5
star
46

adhocore

5
star
47

php-adhocore

A PHP framework I created in early 2013 - inspired by CodeIgniter. Published here for historical reason. It was still a work in progress and was not complete- I am afraid it may as well never be. Check the initial commit.
PHP
5
star
48

leetcode

Just some leet code solutions in Python
Python
3
star
49

zshist

ZSH history manager: merge/deduplicate/normalize history entries for zsh, arrow suggestion UX is improved (plus save space, remove redundancy)
Go
2
star
50

opentelemetry

Temp mirror of old version of open-telemetry/opentelemetry
PHP
2
star
51

import-configuration-jms

Clone of techdivision/import-configuration-jms
PHP
2
star
52

docker-php-fpm-old-alpine

Mirror of official PHP docker images built on alpine 3.13/3/14 instead of 3.15/3.16
Dockerfile
2
star