Code Coverage
 
Lines
Branches
Paths
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
28 / 28
100.00% covered (success)
100.00%
25 / 25
68.42% covered (warning)
68.42%
13 / 19
100.00% covered (success)
100.00%
3 / 3
CRAP
100.00% covered (success)
100.00%
1 / 1
Option
100.00% covered (success)
100.00%
28 / 28
100.00% covered (success)
100.00%
25 / 25
68.42% covered (warning)
68.42%
13 / 19
100.00% covered (success)
100.00%
3 / 3
18.32
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
8 / 8
37.50% covered (danger)
37.50%
3 / 8
100.00% covered (success)
100.00%
1 / 1
7.91
 label
100.00% covered (success)
100.00%
1 / 1
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
 value
100.00% covered (success)
100.00%
19 / 19
100.00% covered (success)
100.00%
13 / 13
88.89% covered (warning)
88.89%
8 / 9
100.00% covered (success)
100.00%
1 / 1
7.07
1<?php
2
3declare(strict_types=1);
4
5namespace Celema\Console;
6
7use BackedEnum;
8use Celema\Console\Exception\InvalidUsage;
9use ReflectionParameter;
10
11/**
12 * An `#[Opt]` parameter of a command's `__invoke()` or of an option group's
13 * constructor.
14 *
15 * @internal
16 */
17final class Option
18{
19    public readonly string $name;
20
21    /**
22     * The `--no-` name that sets a flag to false, if its default is
23     * `true` or `null`.
24     */
25    public readonly ?string $negation;
26
27    /**
28     * @param ?string $group The `__invoke()` parameter of the option group
29     *     declaring the option, if any
30     */
31    public function __construct(
32        public readonly ReflectionParameter $parameter,
33        public readonly Opt $opt,
34        public readonly Type $type,
35        public readonly ?string $group = null,
36    ) {
37        $kebab = Signature::kebab($parameter->getName());
38        $this->name = '--' . $kebab;
39        $this->negation =
40            $type->name === 'bool'
41            && $parameter->isDefaultValueAvailable()
42            && $parameter->getDefaultValue() !== false
43                ? '--no-' . $kebab
44                : null;
45    }
46
47    /**
48     * The `<value>` label of a value-taking option.
49     */
50    public function label(): string
51    {
52        return $this->opt->value === '' ? substr($this->name, offset: 2) : $this->opt->value;
53    }
54
55    /**
56     * Reads the value of the option, which occurs in the input.
57     *
58     * @param string $given The name in the input: the option's own or its
59     *     negation
60     * @param int $occurrences How often the option occurs, bare or with a
61     *     value; Args merges the occurrences
62     * @param bool $bare Whether one of them has no value
63     * @return bool|list<string>|string|int|float|BackedEnum
64     */
65    public function value(
66        Args $args,
67        string $given,
68        int $occurrences,
69        bool $bare,
70    ): bool|array|string|int|float|BackedEnum {
71        $values = $args->opts($given);
72
73        if ($this->type->name === 'bool') {
74            if ($values !== []) {
75                throw new InvalidUsage("Option '{$given}' does not accept a value");
76            }
77
78            return $given !== $this->negation;
79        }
80
81        // Every occurrence needs a value, also when a repetition provides
82        // one: `--host --host=x` must not hide the bare `--host`.
83        if ($bare) {
84            if ($this->opt->bare === null) {
85                throw new InvalidUsage("Option '{$this->name}' requires a value: {$this->name}=<{$this->label()}>");
86            }
87
88            $values[] = $this->opt->bare;
89        }
90
91        if ($this->type->name === 'array') {
92            return $values;
93        }
94
95        if ($occurrences !== 1) {
96            throw new InvalidUsage("Option '{$this->name}' accepts only one value");
97        }
98
99        return (
100            $this->type->convert($values[0])
101                ?? throw new InvalidUsage(
102                    "Option '{$this->name}' expects {$this->type->expected()}, got '{$values[0]}'",
103                )
104        );
105    }
106}