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

nette / command-line / 3678532830

pending completion
3678532830

push

github

David
wip

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

98 of 131 relevant lines covered (74.81%)

0.75 hits per line

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

72.07
/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
        /*
59
        Options:
60
                -p           Short option
61
                --long       Long option
62
                -p | --long  Combined short & long
63

64
                -p           With default value (default: php)
65

66
                -p=<info>
67
                -p <info>
68
                -p info
69
                -p info...
70
                -p <option1|option2>   Repeatable
71
                -p [option1|option2]   Enum
72
        */
73

74

75
        public function __construct(string $help, array $defaults = [])
1✔
76
        {
77
                $this->help = $help;
1✔
78
                $this->options = $defaults;
1✔
79

80
                preg_match_all('#^[ \t]+(--?\w.*?)(?:  .*\(default: (.*)\)|  |\r|$)#m', $help, $lines, PREG_SET_ORDER);
1✔
81
                foreach ($lines as $line) {
1✔
82
                        preg_match_all('#(--?\w[\w-]*)(?:[= ](<.*?>|\[.*?]|\w+)(\.{0,3}))?[ ,|]*#A', $line[1], $m);
1✔
83
                        if (!count($m[0]) || count($m[0]) > 2 || implode('', $m[0]) !== $line[1]) {
1✔
84
                                throw new \InvalidArgumentException("Unable to parse '$line[1]'.");
×
85
                        }
86

87
                        $name = end($m[1]);
1✔
88
                        $opts = $this->options[$name] ?? [];
1✔
89
                        $this->options[$name] = $opts + [
1✔
90
                                self::Argument => (bool) end($m[2]),
1✔
91
                                self::Optional => isset($line[2]) || (substr(end($m[2]), 0, 1) === '[') || isset($opts[self::Default]),
1✔
92
                                self::Repeatable => (bool) end($m[3]),
1✔
93
                                self::Enum => count($enums = explode('|', trim(end($m[2]), '<[]>'))) > 1 ? $enums : null,
1✔
94
                                self::Default => $line[2] ?? null,
1✔
95
                        ];
96
                        if ($name !== $m[1][0]) {
1✔
97
                                $this->aliases[$m[1][0]] = $name;
1✔
98
                        }
99
                }
100

101
                foreach ($this->options as $name => $foo) {
1✔
102
                        if ($name[0] !== '-') {
1✔
103
                                $this->positional[] = $name;
1✔
104
                        }
105
                }
106
        }
1✔
107

108

109
        public function addArgument(string $name, array $options): static
110
        {
111
                $this->positional[] = $name;
×
112
                $this->options[$name] = [
×
113
                        self::Argument => null,
×
114
                        self::Optional => $options[self::Optional] ?? null,
×
115
                        self::Repeatable => $options[self::Repeatable] ?? null,
×
116
                        self::Enum => null,
×
117
                        self::Default => $options[self::Default] ?? null,
×
118
                ];
119
        }
120

121

122
        /**
123
         * @param  string|string[]  $name
124
         */
125
        public function addOption($name): static
126
        {
127
                $this->options[$name] = [
×
128
                        self::Argument => false,
×
129
                        self::Optional => false, // ?
×
130
                        self::Repeatable => false,
×
131
                        self::Enum => null,
×
132
                        self::Default => null,
×
133
                ];
134
        }
135

136

137
        /**
138
         * @param  string|string[]  $name
139
         */
140
        public function addValueOption($name, array $options): static
141
        {
142
                foreach ((array) $name as $name) {
×
143
                        $this->options[$name] = [
×
144
                                self::Argument => true,
×
145
                                self::Optional => false, // ?
×
146
                                self::Repeatable => false,
×
147
                                self::Enum => null,
×
148
                                self::Default => null,
×
149
                        ];
150
                }
151
        }
152

153

154
        /**
155
         * @param  string|string[]  $name
156
         */
157
        public function addEnumeratedOption($name, array $elements, array $options): static
158
        {
159
                foreach ((array) $name as $name) {
×
160
                        $this->options[$name] = [
×
161
                                self::Argument => true,
×
162
                                self::Optional => false, // ?
×
163
                                self::Repeatable => false,
×
164
                                self::Enum => null,
×
165
                                self::Default => null,
×
166
                        ];
167
                }
168
        }
169

170

171
        public static function fromHelp(string $help): static
172
        {
173
        }
174

175

176
        public function parse(?array $args = null): array
1✔
177
        {
178
                if ($args === null) {
1✔
179
                        $args = isset($_SERVER['argv']) ? array_slice($_SERVER['argv'], 1) : [];
×
180
                }
181

182
                $params = [];
1✔
183
                reset($this->positional);
1✔
184
                $i = 0;
1✔
185
                while ($i < count($args)) {
1✔
186
                        $arg = $args[$i++];
1✔
187
                        if ($arg[0] !== '-') {
1✔
188
                                if (!current($this->positional)) {
1✔
189
                                        throw new \Exception("Unexpected parameter $arg.");
1✔
190
                                }
191

192
                                $name = current($this->positional);
1✔
193
                                $this->checkArg($this->options[$name], $arg);
1✔
194
                                if (empty($this->options[$name][self::Repeatable])) {
1✔
195
                                        $params[$name] = $arg;
1✔
196
                                        next($this->positional);
1✔
197
                                } else {
198
                                        $params[$name][] = $arg;
1✔
199
                                }
200

201
                                continue;
1✔
202
                        }
203

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

206
                        if (isset($this->aliases[$name])) {
1✔
207
                                $name = $this->aliases[$name];
1✔
208

209
                        } elseif (!isset($this->options[$name])) {
1✔
210
                                throw new \Exception("Unknown option $name.");
1✔
211
                        }
212

213
                        $opt = $this->options[$name];
1✔
214

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

218
                        } elseif ($arg === true && !empty($opt[self::Argument])) {
1✔
219
                                if (isset($args[$i]) && $args[$i][0] !== '-') {
1✔
220
                                        $arg = $args[$i++];
1✔
221
                                } elseif (empty($opt[self::Optional])) {
1✔
222
                                        throw new \Exception("Option $name requires argument.");
1✔
223
                                }
224
                        }
225

226
                        $this->checkArg($opt, $arg);
1✔
227

228
                        if (
229
                                !empty($opt[self::Enum])
1✔
230
                                && !in_array(is_array($arg) ? reset($arg) : $arg, $opt[self::Enum], true)
1✔
231
                                && !(
232
                                        $opt[self::Optional]
1✔
233
                                        && $arg === true
1✔
234
                                )
235
                        ) {
236
                                throw new \Exception("Value of option $name must be " . implode(', or ', $opt[self::Enum]) . '.');
1✔
237
                        }
238

239
                        if (empty($opt[self::Repeatable])) {
1✔
240
                                $params[$name] = $arg;
1✔
241
                        } else {
242
                                $params[$name][] = $arg;
1✔
243
                        }
244
                }
245

246
                foreach ($this->options as $name => $opt) {
1✔
247
                        if (isset($params[$name])) {
1✔
248
                                continue;
1✔
249
                        } elseif (isset($opt[self::Default])) {
1✔
250
                                $params[$name] = $opt[self::Default];
1✔
251
                        } elseif ($name[0] !== '-' && empty($opt[self::Optional])) {
1✔
252
                                throw new \Exception("Missing required argument <$name>.");
1✔
253
                        } else {
254
                                $params[$name] = null;
1✔
255
                        }
256

257
                        if (!empty($opt[self::Repeatable])) {
1✔
258
                                $params[$name] = (array) $params[$name];
1✔
259
                        }
260
                }
261

262
                return $params;
1✔
263
        }
264

265

266
        public function help(): void
267
        {
268
                // TODO: generate help?
269
                echo $this->help;
×
270
        }
271

272

273
        public function checkArg(array $opt, &$arg): void
1✔
274
        {
275
                if (isset($opt[self::Normalizer])) {
1✔
276
                        $arg = $opt[self::Normalizer]($arg);
1✔
277
                }
278

279
                if (!empty($opt[self::Realpath])) {
1✔
280
                        $path = realpath($arg);
1✔
281
                        if ($path === false) {
1✔
282
                                throw new \Exception("File path '$arg' not found.");
1✔
283
                        }
284

285
                        $arg = $path;
1✔
286
                }
287
        }
1✔
288

289

290
        public function isEmpty(): bool
291
        {
292
                return !isset($_SERVER['argv']) || count($_SERVER['argv']) < 2;
×
293
        }
294
}
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