1: <?php
2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12:
13:
14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72:
73: class Horde_Argv_Parser extends Horde_Argv_OptionContainer
74: {
75: public $standardOptionList = array();
76:
77: protected $_usage;
78: public $optionGroups = array();
79:
80: public function __construct($args = array())
81: {
82: $args = array_merge(array(
83: 'usage' => null,
84: 'optionList' => null,
85: 'optionClass' => 'Horde_Argv_Option',
86: 'version' => null,
87: 'conflictHandler' => "error",
88: 'description' => null,
89: 'formatter' => null,
90: 'addHelpOption' => true,
91: 'prog' => null,
92: 'epilog' => null,
93: 'allowInterspersedArgs' => true,
94: 'allowUnknownArgs' => false,
95: 'ignoreUnknownArgs' => false,
96: ), $args);
97:
98: parent::__construct($args['optionClass'], $args['conflictHandler'], $args['description']);
99: $this->setUsage($args['usage']);
100: $this->prog = $args['prog'];
101: $this->version = $args['version'];
102: $this->allowInterspersedArgs = $args['allowInterspersedArgs'];
103: $this->allowUnknownArgs = $args['allowUnknownArgs'];
104: $this->ignoreUnknownArgs = $args['ignoreUnknownArgs'];
105: if (is_null($args['formatter'])) {
106: $args['formatter'] = new Horde_Argv_IndentedHelpFormatter();
107: }
108: $this->formatter = $args['formatter'];
109: $this->formatter->setParser($this);
110: $this->epilog = $args['epilog'];
111:
112:
113:
114:
115:
116: $this->_populateOptionList($args['optionList'],
117: $args['addHelpOption']);
118:
119: $this->_initParsingState();
120: }
121:
122: 123: 124: 125: 126: 127:
128: public function __destruct()
129: {
130: foreach ($this->optionGroups as &$group) {
131: unset($group);
132: }
133:
134: unset($this->optionList);
135: unset($this->optionGroups);
136: unset($this->formatter);
137: }
138:
139:
140:
141:
142: protected function _createOptionList()
143: {
144: $this->optionList = array();
145: $this->optionGroups = array();
146: $this->_createOptionMappings();
147: }
148:
149: protected function _addHelpOption()
150: {
151: $this->addOption('-h', '--help', array('action' => 'help',
152: 'help' => Horde_Argv_Translation::t("show this help message and exit")));
153: }
154:
155: protected function _addVersionOption()
156: {
157: $this->addOption('--version', array('action' => 'version',
158: 'help' => Horde_Argv_Translation::t("show program's version number and exit")));
159: }
160:
161: protected function _populateOptionList($optionList, $add_help = true)
162: {
163: if ($this->standardOptionList)
164: $this->addOptions($this->standardOptionList);
165: if ($optionList)
166: $this->addOptions($optionList);
167: if ($this->version)
168: $this->_addVersionOption();
169: if ($add_help)
170: $this->_addHelpOption();
171: }
172:
173: protected function _initParsingState()
174: {
175:
176: $this->rargs = null;
177: $this->largs = null;
178: $this->values = null;
179: }
180:
181:
182:
183: public function setUsage($usage)
184: {
185: if (is_null($usage))
186: $this->_usage = '%prog ' . Horde_Argv_Translation::t("[options]");
187: elseif ($usage == Horde_Argv_Option::SUPPRESS_USAGE)
188: $this->_usage = null;
189: else
190: $this->_usage = $usage;
191: }
192:
193: public function enableInterspersedArgs()
194: {
195: $this->allowInterspersedArgs = true;
196: }
197:
198: public function disableInterspersedArgs()
199: {
200: $this->allowInterspersedArgs = false;
201: }
202:
203: public function setDefault($dest, $value)
204: {
205: $this->defaults[$dest] = $value;
206: }
207:
208: public function setDefaults($defaults)
209: {
210: $this->defaults = array_merge($this->defaults, $defaults);
211: }
212:
213: protected function _getAllOptions()
214: {
215: $options = $this->optionList;
216: foreach ($this->optionGroups as $group) {
217: $options = array_merge($options, $group->optionList);
218: }
219: return $options;
220: }
221:
222: public function getDefaultValues()
223: {
224: $defaults = $this->defaults;
225: foreach ($this->_getAllOptions() as $option) {
226: $default = isset($defaults[$option->dest]) ? $defaults[$option->dest] : null;
227: if (is_string($default)) {
228: $opt_str = $option->getOptString();
229: $defaults[$option->dest] = $option->checkValue($opt_str, $default);
230: }
231: }
232:
233: return new Horde_Argv_Values($defaults);
234: }
235:
236:
237:
238:
239: public function addOptionGroup()
240: {
241:
242: $args = func_get_args();
243:
244: if (count($args) && is_string($args[0])) {
245: $groupFactory = new ReflectionClass('Horde_Argv_OptionGroup');
246: array_unshift($args, $this);
247: $group = $groupFactory->newInstanceArgs($args);
248: } elseif (count($args) == 1) {
249: $group = $args[0];
250: if (!$group instanceof Horde_Argv_OptionGroup)
251: throw new InvalidArgumentException("not an OptionGroup instance: " . var_export($group, true));
252: if ($group->parser !== $this)
253: throw new InvalidArgumentException("invalid OptionGroup (wrong parser)");
254: } else {
255: throw new InvalidArgumentException('invalid arguments');
256: }
257:
258: $this->optionGroups[] = $group;
259: $this->defaults = array_merge($this->defaults, $group->defaults);
260: return $group;
261: }
262:
263: public function getOptionGroup($opt_str)
264: {
265: if (isset($this->shortOpt[$opt_str])) {
266: $option = $this->shortOpt[$opt_str];
267: } elseif (isset($this->longOpt[$opt_str])) {
268: $option = $this->longOpt[$opt_str];
269: } else {
270: return null;
271: }
272:
273: if ($option->container !== $this) {
274: return $option->container;
275: }
276:
277: return null;
278: }
279:
280:
281:
282: protected function _getArgs($args = null)
283: {
284: if (is_null($args)) {
285: $args = $_SERVER['argv'];
286: array_shift($args);
287: return $args;
288: } else {
289: return $args;
290: }
291: }
292:
293: 294: 295: 296: 297: 298: 299: 300: 301:
302: public function parseArgs($args = null, $values = null)
303: {
304: $rargs = $this->_getArgs($args);
305: $largs = array();
306: if (is_null($values))
307: $values = $this->getDefaultValues();
308:
309:
310:
311:
312:
313:
314:
315:
316:
317:
318: $this->rargs =& $rargs;
319: $this->largs =& $largs;
320: $this->values = $values;
321:
322: try {
323: $this->_processArgs($largs, $rargs, $values);
324: } catch (Horde_Argv_BadOptionException $e) {
325: $this->parserError($e->getMessage());
326: } catch (Horde_Argv_OptionValueException $e) {
327: $this->parserError($e->getMessage());
328: }
329:
330: $args = array_merge($largs, $rargs);
331: return $this->checkValues($values, $args);
332: }
333:
334: 335: 336: 337: 338: 339: 340:
341: public function checkValues($values, $args)
342: {
343: return array($values, $args);
344: }
345:
346: 347: 348: 349: 350: 351: 352: 353: 354: 355:
356: protected function _processArgs(&$largs, &$rargs, &$values)
357: {
358: while ($rargs) {
359: $arg = $rargs[0];
360:
361:
362:
363: if ($arg == '--') {
364: array_shift($rargs);
365: return;
366: } elseif (substr($arg, 0, 2) == '--') {
367:
368: $this->_processLongOpt($rargs, $values);
369: } elseif (substr($arg, 0, 1) == '-' && strlen($arg) > 1) {
370:
371:
372: $this->_processShortOpts($rargs, $values);
373: } elseif ($this->allowInterspersedArgs) {
374: $largs[] = $arg;
375: array_shift($rargs);
376: } else {
377:
378: return;
379: }
380: }
381:
382:
383:
384:
385:
386:
387:
388:
389:
390:
391:
392:
393:
394:
395:
396:
397:
398:
399:
400:
401: }
402:
403: 404: 405: 406: 407: 408: 409:
410: protected function _matchLongOpt($opt)
411: {
412: return self::matchAbbrev($opt, $this->longOpt);
413: }
414:
415: 416: 417: 418: 419: 420: 421:
422: public static function matchAbbrev($s, $wordmap)
423: {
424:
425: if (array_key_exists($s, $wordmap)) {
426: return $s;
427: }
428:
429:
430: $possibilities = array();
431: foreach (array_keys($wordmap) as $word) {
432: if (strncmp($word, $s, strlen($s)) === 0) {
433: $possibilities[] = $word;
434: }
435: }
436:
437:
438: if (count($possibilities) == 1) {
439: return $possibilities[0];
440: } elseif (!$possibilities) {
441: throw new Horde_Argv_BadOptionException($s);
442: } else {
443:
444: sort($possibilities);
445: throw new Horde_Argv_AmbiguousOptionException($s, $possibilities);
446: }
447: }
448:
449: protected function _processLongOpt(&$rargs, &$values)
450: {
451: $arg = array_shift($rargs);
452:
453:
454:
455: if (strpos($arg, '=') !== false) {
456: list($opt, $next_arg) = explode('=', $arg, 2);
457: array_unshift($rargs, $next_arg);
458: $had_explicit_value = true;
459: } else {
460: $opt = $arg;
461: $had_explicit_value = false;
462: }
463:
464: try {
465: $opt = $this->_matchLongOpt($opt);
466: $option = $this->longOpt[$opt];
467: } catch (Horde_Argv_BadOptionException $e) {
468: if ($this->ignoreUnknownArgs) {
469: return;
470: }
471: if ($this->allowUnknownArgs) {
472: $option = $this->addOption($opt, array('default' => true, 'action' => 'append'));
473: } else {
474: throw $e;
475: }
476: }
477:
478: if ($option->takesValue()) {
479: $nargs = $option->nargs;
480: if (count($rargs) < $nargs) {
481: if (!$option->hasDefault()) {
482: if ($nargs == 1) {
483: $this->parserError(sprintf(Horde_Argv_Translation::t("%s option requires an argument"), $opt));
484: } else {
485: $this->parserError(sprintf(Horde_Argv_Translation::t("%s option requires %d arguments"), $opt, $nargs));
486: }
487: }
488: } elseif ($nargs == 1) {
489: $value = array_shift($rargs);
490: } else {
491: $value = array_splice($rargs, 0, $nargs);
492: }
493:
494: } elseif ($had_explicit_value) {
495: $this->parserError(sprintf(Horde_Argv_Translation::t("%s option does not take a value"), $opt));
496:
497: } else {
498: $value = null;
499: }
500:
501: $option->process($opt, $value, $values, $this);
502: }
503:
504: protected function _processShortOpts(&$rargs, &$values)
505: {
506: $arg = array_shift($rargs);
507: $stop = false;
508: $i = 1;
509: for ($c = 1, $c_max = strlen($arg); $c < $c_max; $c++) {
510: $ch = $arg[$c];
511: $opt = '-' . $ch;
512: $option = isset($this->shortOpt[$opt]) ? $this->shortOpt[$opt] : null;
513: $i++;
514:
515: if (!$option) {
516: if ($this->allowUnknownArgs) {
517: $option = $this->addOption($opt, array('default' => true, 'action' => 'append'));
518: } else {
519: throw new Horde_Argv_BadOptionException($opt);
520: }
521: }
522:
523: if ($option->takesValue()) {
524:
525:
526: if ($i < strlen($arg)) {
527: array_unshift($rargs, substr($arg, $i));
528: $stop = true;
529: }
530:
531: $nargs = $option->nargs;
532: if (count($rargs) < $nargs) {
533: if (!$option->hasDefault()) {
534: if ($nargs == 1) {
535: $this->parserError(sprintf(Horde_Argv_Translation::t("%s option requires an argument"), $opt));
536: } else {
537: $this->parserError(sprintf(Horde_Argv_Translation::t("%s option requires %d arguments"), $opt, $nargs));
538: }
539: }
540: } elseif ($nargs == 1) {
541: $value = array_shift($rargs);
542: } else {
543: $value = array_splice($rargs, 0, $nargs);
544: }
545:
546: } else {
547:
548: $value = null;
549: }
550:
551: $option->process($opt, $value, $values, $this);
552:
553: if ($stop) { break; }
554: }
555: }
556:
557:
558:
559: public function getProgName()
560: {
561: if (is_null($this->prog))
562: return basename($_SERVER['argv'][0]);
563: else
564: return $this->prog;
565: }
566:
567: public function expandProgName($s)
568: {
569: return str_replace("%prog", $this->getProgName(), $s);
570: }
571:
572: public function getDescription()
573: {
574: return $this->expandProgName($this->description);
575: }
576:
577: public function parserExit($status = 0, $msg = null)
578: {
579: if ($msg)
580: fwrite(STDERR, $msg);
581: exit($status);
582: }
583:
584: 585: 586: 587: 588: 589: 590:
591: public function parserError($msg)
592: {
593: $this->printUsage(STDERR);
594: $this->parserExit(2, sprintf("%s: error: %s\n", $this->getProgName(), $msg));
595: }
596:
597: public function getUsage($formatter = null)
598: {
599: if (is_null($formatter))
600: $formatter = $this->formatter;
601: if ($this->_usage)
602: return $formatter->formatUsage($this->expandProgName($this->_usage));
603: else
604: return '';
605: }
606:
607: 608: 609: 610: 611: 612: 613: 614: 615:
616: public function printUsage($file = null)
617: {
618: if (!$this->_usage)
619: return;
620:
621: if (is_null($file))
622: echo $this->getUsage();
623: else
624: fwrite($file, $this->getUsage());
625: }
626:
627: public function getVersion()
628: {
629: if ($this->version)
630: return $this->expandProgName($this->version);
631: else
632: return '';
633: }
634:
635: 636: 637: 638: 639: 640: 641: 642:
643: public function printVersion($file = null)
644: {
645: if (!$this->version)
646: return;
647:
648: if (is_null($file))
649: echo $this->getVersion() . "\n";
650: else
651: fwrite($file, $this->getVersion() . "\n");
652: }
653:
654: public function formatOptionHelp($formatter = null)
655: {
656: if (is_null($formatter))
657: $formatter = $this->formatter;
658: $formatter->storeOptionStrings($this);
659: $result = array();
660: $result[] = $formatter->formatHeading(Horde_Argv_Translation::t("Options"));
661: $formatter->indent();
662: if ($this->optionList) {
663: $result[] = parent::formatOptionHelp($formatter);
664: $result[] = "\n";
665: }
666: foreach ($this->optionGroups as $group) {
667: $result[] = $group->formatHelp($formatter);
668: $result[] = "\n";
669: }
670: $formatter->dedent();
671:
672: array_pop($result);
673: return implode('', $result);
674: }
675:
676: public function formatEpilog($formatter)
677: {
678: return $formatter->formatEpilog($this->epilog);
679: }
680:
681: public function formatHelp($formatter = null)
682: {
683: if (is_null($formatter))
684: $formatter = $this->formatter;
685: $result = array();
686: if ($this->_usage)
687: $result[] = $this->getUsage($formatter) . "\n";
688: if ($this->description)
689: $result[] = $this->formatDescription($formatter) . "\n";
690: $result[] = $this->formatOptionHelp($formatter);
691: $result[] = $this->formatEpilog($formatter);
692: return implode('', $result);
693: }
694:
695: 696: 697: 698: 699: 700:
701: public function printHelp($file = null)
702: {
703: if (is_null($file))
704: echo $this->formatHelp();
705: else
706: fwrite($file, $this->formatHelp());
707: }
708:
709: }
710: