Code Coverage
 
Lines
Branches
Paths
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
11 / 11
90.48% covered (success)
90.48%
19 / 21
23.53% covered (danger)
23.53%
8 / 34
50.00% covered (danger)
50.00%
1 / 2
CRAP
0.00% covered (danger)
0.00%
0 / 1
Opt
100.00% covered (success)
100.00%
11 / 11
90.48% covered (success)
90.48%
19 / 21
23.53% covered (danger)
23.53%
8 / 34
100.00% covered (success)
100.00%
2 / 2
36.62
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
6 / 6
88.24% covered (warning)
88.24%
15 / 17
18.75% covered (danger)
18.75%
6 / 32
100.00% covered (success)
100.00%
1 / 1
25.31
 of
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
1<?php
2
3declare(strict_types=1);
4
5namespace Celema\Console;
6
7use Attribute;
8use ReflectionAttribute;
9use ReflectionClass;
10use ValueError;
11
12/**
13 * Describes one option of a command in its help output.
14 *
15 * Give the flag names and, for value-taking options, a `value` label; the
16 * `--opt=<value>` notation is rendered by the runner so it cannot drift from
17 * the `=`-only parser. Omit `value` for a boolean flag; set `optionalValue`
18 * for a flag whose value is optional (`--opt[=<value>]`). A `default`
19 * renders as `[default: ...]` after the description.
20 *
21 * @api
22 */
23#[Attribute(Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)]
24final class Opt
25{
26    // Attribute parameters are passed as named arguments; a parameter
27    // object would defeat the attribute syntax.
28    // @mago-expect lint:excessive-parameter-list
29    public function __construct(
30        public readonly string $long,
31        public readonly string $description,
32        public readonly string $short = '',
33        public readonly string $value = '',
34        public readonly bool $optionalValue = false,
35        public readonly string $default = '',
36    ) {
37        if (preg_match('/^--[^-=\s][^=\s]*$/', $long) !== 1) {
38            throw new ValueError("Invalid option name '{$long}'");
39        }
40
41        if ($short !== '' && preg_match('/^-[^-=\s][^=\s]*$/', $short) !== 1) {
42            throw new ValueError("Invalid short option name '{$short}'");
43        }
44
45        if ($optionalValue && $value === '') {
46            throw new ValueError("Option '{$long}' declares an optional value without a value label");
47        }
48    }
49
50    /**
51     * Reads all option attributes off a command instance or class.
52     *
53     * @param class-string|object $command
54     * @return list<self>
55     */
56    public static function of(object|string $command): array
57    {
58        $class = is_object($command) ? $command::class : $command;
59
60        return array_map(
61            static fn(ReflectionAttribute $attribute): self => $attribute->newInstance(),
62            new ReflectionClass($class)->getAttributes(self::class),
63        );
64    }
65}