1: <?php
2:
3: 4: 5: 6:
7:
8: namespace Tester\Runner;
9:
10:
11: 12: 13: 14: 15:
16: class CommandLine
17: {
18: const
19: ARGUMENT = 'argument',
20: OPTIONAL = 'optional',
21: REPEATABLE = 'repeatable',
22: REALPATH = 'realpath',
23: VALUE = 'default';
24:
25:
26: private $options = array();
27:
28:
29: private $aliases = array();
30:
31:
32: private $positional = array();
33:
34:
35: private $help;
36:
37:
38: public function __construct($help, array $defaults = array())
39: {
40: $this->help = $help;
41: $this->options = $defaults;
42:
43: preg_match_all('#^[ \t]+(--?\w.*?)(?: .*\(default: (.*)\)| |\r|$)#m', $help, $lines, PREG_SET_ORDER);
44: foreach ($lines as $line) {
45: preg_match_all('#(--?\w+)(?:[= ](<.*?>|\[.*?]|\w+)(\.{0,3}))?[ ,|]*#A', $line[1], $m);
46: if (!count($m[0]) || count($m[0]) > 2 || implode('', $m[0]) !== $line[1]) {
47: throw new \InvalidArgumentException("Unable to parse '$line[1]'.");
48: }
49:
50: $name = end($m[1]);
51: $opts = isset($this->options[$name]) ? $this->options[$name] : array();
52: $this->options[$name] = $opts + array(
53: self::ARGUMENT => (bool) end($m[2]),
54: self::OPTIONAL => isset($line[2]) || (substr(end($m[2]), 0, 1) === '[') || isset($opts[self::VALUE]),
55: self::REPEATABLE => (bool) end($m[3]),
56: self::VALUE => isset($line[2]) ? $line[2] : NULL,
57: );
58: if ($name !== $m[1][0]) {
59: $this->aliases[$m[1][0]] = $name;
60: }
61: }
62:
63: foreach ($this->options as $name => $foo) {
64: if ($name[0] !== '-') {
65: $this->positional[] = $name;
66: }
67: }
68: }
69:
70:
71: public function parse(array $args = NULL)
72: {
73: if ($args === NULL) {
74: $args = isset($_SERVER['argv']) ? array_slice($_SERVER['argv'], 1) : array();
75: }
76: $params = array();
77: reset($this->positional);
78: $i = 0;
79: while ($i < count($args)) {
80: $arg = $args[$i++];
81: if ($arg[0] !== '-') {
82: if (!current($this->positional)) {
83: throw new \Exception("Unexpected parameter $arg.");
84: }
85: $name = current($this->positional);
86: $this->checkArg($this->options[$name], $arg);
87: if (empty($this->options[$name][self::REPEATABLE])) {
88: $params[$name] = $arg;
89: next($this->positional);
90: } else {
91: $params[$name][] = $arg;
92: }
93: continue;
94: }
95:
96: list($name, $arg) = strpos($arg, '=') ? explode('=', $arg, 2) : array($arg, TRUE);
97:
98: if (isset($this->aliases[$name])) {
99: $name = $this->aliases[$name];
100:
101: } elseif (!isset($this->options[$name])) {
102: throw new \Exception("Unknown option $name.");
103: }
104:
105: $opt = $this->options[$name];
106:
107: if ($arg !== TRUE && empty($opt[self::ARGUMENT])) {
108: throw new \Exception("Option $name has not argument.");
109:
110: } elseif ($arg === TRUE && !empty($opt[self::ARGUMENT])) {
111: if (isset($args[$i]) && $args[$i][0] !== '-') {
112: $arg = $args[$i++];
113: } elseif (empty($opt[self::OPTIONAL])) {
114: throw new \Exception("Option $name requires argument.");
115: }
116: }
117: $this->checkArg($opt, $arg);
118:
119: if (empty($opt[self::REPEATABLE])) {
120: $params[$name] = $arg;
121: } else {
122: $params[$name][] = $arg;
123: }
124: }
125:
126: foreach ($this->options as $name => $opt) {
127: if (isset($params[$name])) {
128: continue;
129: } elseif (isset($opt[self::VALUE])) {
130: $params[$name] = $opt[self::VALUE];
131: } elseif ($name[0] !== '-' && empty($opt[self::OPTIONAL])) {
132: throw new \Exception("Missing required argument <$name>.");
133: } else {
134: $params[$name] = NULL;
135: }
136: if (!empty($opt[self::REPEATABLE])) {
137: $params[$name] = (array) $params[$name];
138: }
139: }
140: return $params;
141: }
142:
143:
144: public function help()
145: {
146: echo $this->help;
147: }
148:
149:
150: public function checkArg(array $opt, & $arg)
151: {
152: if (!empty($opt[self::REALPATH])) {
153: $path = realpath($arg);
154: if ($path === FALSE) {
155: throw new \Exception("File path '$arg' not found.");
156: }
157: $arg = $path;
158: }
159: }
160:
161:
162: public function isEmpty()
163: {
164: return !isset($_SERVER['argv']) || count($_SERVER['argv']) < 2;
165: }
166:
167: }
168: