• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

nette / command-line / 3745460004

pending completion
3745460004

push

github

David
Parser: added Normalizer

4 of 4 new or added lines in 1 file covered. (100.0%)

98 of 104 relevant lines covered (94.23%)

0.94 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

95.24
/src/CommandLine/Parser.php
1
<?php
2

3
/**
4
 * This file is part of the Nette Framework (https://nette.org)
5
 * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
6
 */
7

8
declare(strict_types=1);
9

10
namespace Nette\CommandLine;
11

12

13
/**
14
 * Stupid command line arguments parser.
15
 */
16
class Parser
17
{
18
        public const
19
                Argument = 'argument',
20
                Optional = 'optional',
21
                Repeatable = 'repeatable',
22
                Enum = 'enum',
23
                Realpath = 'realpath',
24
                Normalizer = 'normalizer',
25
                Default = 'default';
26

27
        /** @deprecated use Parser::Argument */
28
        public const ARGUMENT = self::Argument;
29

30
        /** @deprecated use Parser::Optional */
31
        public const OPTIONAL = self::Optional;
32

33
        /** @deprecated use Parser::Repeatable */
34
        public const REPEATABLE = self::Repeatable;
35

36
        /** @deprecated use Parser::Enum */
37
        public const ENUM = self::Enum;
38

39
        /** @deprecated use Parser::Realpath */
40
        public const REALPATH = self::Realpath;
41

42
        /** @deprecated use Parser::Default */
43
        public const VALUE = self::Default;
44

45

46
        /** @var array[] */
47
        private array $options = [];
48

49
        /** @var string[] */
50
        private array $aliases = [];
51

52
        /** @var string[] */
53
        private array $positional = [];
54

55
        private string $help;
56

57

58
        public function __construct(string $help, array $defaults = [])
1✔
59
        {
60
                $this->help = $help;
1✔
61
                $this->options = $defaults;
1✔
62

63
                preg_match_all('#^[ \t]+(--?\w.*?)(?:  .*\(default: (.*)\)|  |\r|$)#m', $help, $lines, PREG_SET_ORDER);
1✔
64
                foreach ($lines as $line) {
1✔
65
                        preg_match_all('#(--?\w[\w-]*)(?:[= ](<.*?>|\[.*?]|\w+)(\.{0,3}))?[ ,|]*#A', $line[1], $m);
1✔
66
                        if (!count($m[0]) || count($m[0]) > 2 || implode('', $m[0]) !== $line[1]) {
1✔
67
                                throw new \InvalidArgumentException("Unable to parse '$line[1]'.");
×
68
                        }
69

70
                        $name = end($m[1]);
1✔
71
                        $opts = $this->options[$name] ?? [];
1✔
72
                        $this->options[$name] = $opts + [
1✔
73
                                self::Argument => (bool) end($m[2]),
1✔
74
                                self::Optional => isset($line[2]) || (substr(end($m[2]), 0, 1) === '[') || isset($opts[self::Default]),
1✔
75
                                self::Repeatable => (bool) end($m[3]),
1✔
76
                                self::Enum => count($enums = explode('|', trim(end($m[2]), '<[]>'))) > 1 ? $enums : null,
1✔
77
                                self::Default => $line[2] ?? null,
1✔
78
                        ];
79
                        if ($name !== $m[1][0]) {
1✔
80
                                $this->aliases[$m[1][0]] = $name;
1✔
81
                        }
82
                }
83

84
                foreach ($this->options as $name => $foo) {
1✔
85
                        if ($name[0] !== '-') {
1✔
86
                                $this->positional[] = $name;
1✔
87
                        }
88
                }
89
        }
1✔
90

91

92
        public function parse(?array $args = null): array
1✔
93
        {
94
                if ($args === null) {
1✔
95
                        $args = isset($_SERVER['argv']) ? array_slice($_SERVER['argv'], 1) : [];
×
96
                }
97

98
                $params = [];
1✔
99
                reset($this->positional);
1✔
100
                $i = 0;
1✔
101
                while ($i < count($args)) {
1✔
102
                        $arg = $args[$i++];
1✔
103
                        if ($arg[0] !== '-') {
1✔
104
                                if (!current($this->positional)) {
1✔
105
                                        throw new \Exception("Unexpected parameter $arg.");
1✔
106
                                }
107

108
                                $name = current($this->positional);
1✔
109
                                $this->checkArg($this->options[$name], $arg);
1✔
110
                                if (empty($this->options[$name][self::Repeatable])) {
1✔
111
                                        $params[$name] = $arg;
1✔
112
                                        next($this->positional);
1✔
113
                                } else {
114
                                        $params[$name][] = $arg;
1✔
115
                                }
116

117
                                continue;
1✔
118
                        }
119

120
                        [$name, $arg] = strpos($arg, '=') ? explode('=', $arg, 2) : [$arg, true];
1✔
121

122
                        if (isset($this->aliases[$name])) {
1✔
123
                                $name = $this->aliases[$name];
1✔
124

125
                        } elseif (!isset($this->options[$name])) {
1✔
126
                                throw new \Exception("Unknown option $name.");
1✔
127
                        }
128

129
                        $opt = $this->options[$name];
1✔
130

131
                        if ($arg !== true && empty($opt[self::Argument])) {
1✔
132
                                throw new \Exception("Option $name has not argument.");
1✔
133

134
                        } elseif ($arg === true && !empty($opt[self::Argument])) {
1✔
135
                                if (isset($args[$i]) && $args[$i][0] !== '-') {
1✔
136
                                        $arg = $args[$i++];
1✔
137
                                } elseif (empty($opt[self::Optional])) {
1✔
138
                                        throw new \Exception("Option $name requires argument.");
1✔
139
                                }
140
                        }
141

142
                        $this->checkArg($opt, $arg);
1✔
143

144
                        if (
145
                                !empty($opt[self::Enum])
1✔
146
                                && !in_array(is_array($arg) ? reset($arg) : $arg, $opt[self::Enum], true)
1✔
147
                                && !(
148
                                        $opt[self::Optional]
1✔
149
                                        && $arg === true
1✔
150
                                )
151
                        ) {
152
                                throw new \Exception("Value of option $name must be " . implode(', or ', $opt[self::Enum]) . '.');
1✔
153
                        }
154

155
                        if (empty($opt[self::Repeatable])) {
1✔
156
                                $params[$name] = $arg;
1✔
157
                        } else {
158
                                $params[$name][] = $arg;
1✔
159
                        }
160
                }
161

162
                foreach ($this->options as $name => $opt) {
1✔
163
                        if (isset($params[$name])) {
1✔
164
                                continue;
1✔
165
                        } elseif (isset($opt[self::Default])) {
1✔
166
                                $params[$name] = $opt[self::Default];
1✔
167
                        } elseif ($name[0] !== '-' && empty($opt[self::Optional])) {
1✔
168
                                throw new \Exception("Missing required argument <$name>.");
1✔
169
                        } else {
170
                                $params[$name] = null;
1✔
171
                        }
172

173
                        if (!empty($opt[self::Repeatable])) {
1✔
174
                                $params[$name] = (array) $params[$name];
1✔
175
                        }
176
                }
177

178
                return $params;
1✔
179
        }
180

181

182
        public function help(): void
183
        {
184
                echo $this->help;
×
185
        }
186

187

188
        public function checkArg(array $opt, &$arg): void
1✔
189
        {
190
                if (isset($opt[self::Normalizer])) {
1✔
191
                        $arg = $opt[self::Normalizer]($arg);
1✔
192
                }
193

194
                if (!empty($opt[self::Realpath])) {
1✔
195
                        $path = realpath($arg);
1✔
196
                        if ($path === false) {
1✔
197
                                throw new \Exception("File path '$arg' not found.");
1✔
198
                        }
199

200
                        $arg = $path;
1✔
201
                }
202
        }
1✔
203

204

205
        public function isEmpty(): bool
206
        {
207
                return !isset($_SERVER['argv']) || count($_SERVER['argv']) < 2;
×
208
        }
209
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc