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}

Branches

Below are the source code lines that represent each code branch as identified by Xdebug. Please note a branch is not necessarily coterminous with a line, a line may contain multiple branches and therefore show up more than once. Please also be aware that some branches may be implicit rather than explicit, e.g. an if statement always has an else as part of its logical flow even if you didn't write one.

Option->__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()
41            && $parameter->isDefaultValueAvailable()
42            && $parameter->getDefaultValue() !== false
42            && $parameter->getDefaultValue() !== false
43                ? '--no-' . $kebab
39        $this->negation =
40            $type->name === 'bool'
41            && $parameter->isDefaultValueAvailable()
42            && $parameter->getDefaultValue() !== false
43                ? '--no-' . $kebab
44                : null;
39        $this->negation =
40            $type->name === 'bool'
41            && $parameter->isDefaultValueAvailable()
42            && $parameter->getDefaultValue() !== false
43                ? '--no-' . $kebab
44                : null;
45    }
Option->label
52        return $this->opt->value === '' ? substr($this->name, offset: 2) : $this->opt->value;
52        return $this->opt->value === '' ? substr($this->name, offset: 2) : $this->opt->value;
52        return $this->opt->value === '' ? substr($this->name, offset: 2) : $this->opt->value;
52        return $this->opt->value === '' ? substr($this->name, offset: 2) : $this->opt->value;
53    }
Option->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");
78            return $given !== $this->negation;
83        if ($bare) {
84            if ($this->opt->bare === null) {
85                throw new InvalidUsage("Option '{$this->name}' requires a value: {$this->name}=<{$this->label()}>");
88            $values[] = $this->opt->bare;
89        }
90
91        if ($this->type->name === 'array') {
91        if ($this->type->name === 'array') {
92            return $values;
95        if ($occurrences !== 1) {
96            throw new InvalidUsage("Option '{$this->name}' accepts only one value");
100            $this->type->convert($values[0])
101                ?? throw new InvalidUsage(
102                    "Option '{$this->name}' expects {$this->type->expected()}, got '{$values[0]}'",
103                )
104        );
105    }