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

nette / command-line / 3678425886

pending completion
3678425886

push

github

David
constants are PascalCase

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

96 of 102 relevant lines covered (94.12%)

0.94 hits per line

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

95.12
/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
                Default = 'default';
25

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

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

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

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

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

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

44

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

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

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

54
        private string $help;
55

56

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

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

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

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

90

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

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

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

116
                                continue;
1✔
117
                        }
118

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

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

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

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

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

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

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

152
                        $this->checkArg($opt, $arg);
1✔
153

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

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

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

177
                return $params;
1✔
178
        }
179

180

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

186

187
        public function checkArg(array $opt, &$arg): void
1✔
188
        {
189
                if (!empty($opt[self::Realpath])) {
1✔
190
                        $path = realpath($arg);
1✔
191
                        if ($path === false) {
1✔
192
                                throw new \Exception("File path '$arg' not found.");
1✔
193
                        }
194

195
                        $arg = $path;
1✔
196
                }
197
        }
1✔
198

199

200
        public function isEmpty(): bool
201
        {
202
                return !isset($_SERVER['argv']) || count($_SERVER['argv']) < 2;
×
203
        }
204
}
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