Code Coverage
 
Lines
Branches
Paths
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
38 / 38
100.00% covered (success)
100.00%
48 / 48
13.33% covered (danger)
13.33%
8 / 60
100.00% covered (success)
100.00%
5 / 5
CRAP
100.00% covered (success)
100.00%
1 / 1
Help
100.00% covered (success)
100.00%
38 / 38
100.00% covered (success)
100.00%
48 / 48
13.33% covered (danger)
13.33%
8 / 60
100.00% covered (success)
100.00%
5 / 5
337.07
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 show
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
15 / 15
8.33% covered (danger)
8.33%
2 / 24
100.00% covered (success)
100.00%
1 / 1
33.73
 showArguments
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
11 / 11
28.57% covered (danger)
28.57%
2 / 7
100.00% covered (success)
100.00%
1 / 1
14.11
 showOptions
100.00% covered (success)
100.00%
18 / 18
100.00% covered (success)
100.00%
20 / 20
7.41% covered (danger)
7.41%
2 / 27
100.00% covered (success)
100.00%
1 / 1
73.30
 showFor
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3declare(strict_types=1);
4
5namespace Celema\Console;
6
7/**
8 * Renders a command's help screen from its attributes.
9 *
10 * Used by the Runner for `help <command>`; commands that intercept a
11 * `--help` flag themselves can render the same screen via `showFor()`.
12 *
13 * @api
14 */
15final class Help
16{
17    public function __construct(
18        private readonly Io $io,
19    ) {}
20
21    /**
22     * @param list<Opt> $opts
23     * @param list<Arg> $arguments
24     */
25    public function show(Command $meta, array $opts = [], array $arguments = []): void
26    {
27        $script = $_SERVER['argv'][0] ?? '';
28
29        if ($meta->description !== '') {
30            $this->io->echo("<yellow>Description:</yellow>\n  {$meta->description}\n\n");
31        }
32
33        $usage = "<yellow>Usage:</yellow>\n  php {$script} {$meta->full()}";
34
35        foreach ($arguments as $argument) {
36            // Escaped: the <name> notation must not parse as markup.
37            $name = $this->io->escape("<{$argument->name}>") . ($argument->variadic ? '...' : '');
38            $usage .= $argument->optional ? " [{$name}]" : " {$name}";
39        }
40
41        $this->io->echo($usage . ($opts === [] ? "\n" : " [options]\n"));
42        $this->showArguments($arguments);
43        $this->showOptions($opts);
44    }
45
46    /** @param list<Arg> $arguments */
47    private function showArguments(array $arguments): void
48    {
49        if ($arguments === []) {
50            return;
51        }
52
53        $this->io->echo("\n<yellow>Arguments:</yellow>\n");
54
55        foreach ($arguments as $argument) {
56            $name = $this->io->escape("<{$argument->name}>") . ($argument->variadic ? '...' : '');
57            $this->io->echo("    <green>{$name}</green>\n");
58
59            if ($argument->description !== '') {
60                $this->io->echo($this->io->indent($argument->description, 8, 80) . "\n");
61            }
62        }
63    }
64
65    /** @param list<Opt> $opts */
66    private function showOptions(array $opts): void
67    {
68        if ($opts === []) {
69            return;
70        }
71
72        $this->io->echo("\n<yellow>Options:</yellow>\n");
73
74        foreach ($opts as $opt) {
75            $suffix = match (true) {
76                $opt->value === '' => '',
77                $opt->optionalValue => "[=<{$opt->value}>]",
78                default => "=<{$opt->value}>",
79            };
80
81            $option = $opt->short === ''
82                ? $opt->long . $suffix
83                : "{$opt->short}{$suffix}{$opt->long}{$suffix}";
84
85            $description = $opt->default === ''
86                ? $opt->description
87                : "{$opt->description} [default: {$opt->default}]";
88
89            $this->io->echo('    <green>' . $this->io->escape($option) . "</green>\n");
90
91            if ($description !== '') {
92                $this->io->echo($this->io->indent($description, 8, 80) . "\n");
93            }
94        }
95    }
96
97    /**
98     * Renders help for a command instance or class from its attributes.
99     *
100     * @param class-string|object $command
101     */
102    public function showFor(object|string $command): void
103    {
104        $this->show(Command::of($command), Opt::of($command), Arg::of($command));
105    }
106}