ILIAS  release_5-4 Revision v5.4.26-12-gabc799a52e6
Cli.php
Go to the documentation of this file.
1<?php
2
3namespace Sabre\VObject;
4
5use
6 InvalidArgumentException;
7
15class Cli {
16
22 protected $quiet = false;
23
29 protected $showHelp = false;
30
36 protected $format;
37
43 protected $pretty;
44
50 protected $inputPath;
51
57 protected $outputPath;
58
64 protected $stdout;
65
71 protected $stdin;
72
78 protected $stderr;
79
85 protected $inputFormat;
86
92 protected $forgiving = false;
93
99 function main(array $argv) {
100
101 // @codeCoverageIgnoreStart
102 // We cannot easily test this, so we'll skip it. Pretty basic anyway.
103
104 if (!$this->stderr) {
105 $this->stderr = fopen('php://stderr', 'w');
106 }
107 if (!$this->stdout) {
108 $this->stdout = fopen('php://stdout', 'w');
109 }
110 if (!$this->stdin) {
111 $this->stdin = fopen('php://stdin', 'r');
112 }
113
114 // @codeCoverageIgnoreEnd
115
116
117 try {
118
119 list($options, $positional) = $this->parseArguments($argv);
120
121 if (isset($options['q'])) {
122 $this->quiet = true;
123 }
124 $this->log($this->colorize('green', "sabre/vobject ") . $this->colorize('yellow', Version::VERSION));
125
126 foreach ($options as $name => $value) {
127
128 switch ($name) {
129
130 case 'q' :
131 // Already handled earlier.
132 break;
133 case 'h' :
134 case 'help' :
135 $this->showHelp();
136 return 0;
137 break;
138 case 'format' :
139 switch ($value) {
140
141 // jcard/jcal documents
142 case 'jcard' :
143 case 'jcal' :
144
145 // specific document versions
146 case 'vcard21' :
147 case 'vcard30' :
148 case 'vcard40' :
149 case 'icalendar20' :
150
151 // specific formats
152 case 'json' :
153 case 'mimedir' :
154
155 // icalendar/vcad
156 case 'icalendar' :
157 case 'vcard' :
158 $this->format = $value;
159 break;
160
161 default :
162 throw new InvalidArgumentException('Unknown format: ' . $value);
163
164 }
165 break;
166 case 'pretty' :
167 if (version_compare(PHP_VERSION, '5.4.0') >= 0) {
168 $this->pretty = true;
169 }
170 break;
171 case 'forgiving' :
172 $this->forgiving = true;
173 break;
174 case 'inputformat' :
175 switch ($value) {
176 // json formats
177 case 'jcard' :
178 case 'jcal' :
179 case 'json' :
180 $this->inputFormat = 'json';
181 break;
182
183 // mimedir formats
184 case 'mimedir' :
185 case 'icalendar' :
186 case 'vcard' :
187 case 'vcard21' :
188 case 'vcard30' :
189 case 'vcard40' :
190 case 'icalendar20' :
191
192 $this->inputFormat = 'mimedir';
193 break;
194
195 default :
196 throw new InvalidArgumentException('Unknown format: ' . $value);
197
198 }
199 break;
200 default :
201 throw new InvalidArgumentException('Unknown option: ' . $name);
202
203 }
204
205 }
206
207 if (count($positional) === 0) {
208 $this->showHelp();
209 return 1;
210 }
211
212 if (count($positional) === 1) {
213 throw new InvalidArgumentException('Inputfile is a required argument');
214 }
215
216 if (count($positional) > 3) {
217 throw new InvalidArgumentException('Too many arguments');
218 }
219
220 if (!in_array($positional[0], ['validate', 'repair', 'convert', 'color'])) {
221 throw new InvalidArgumentException('Uknown command: ' . $positional[0]);
222 }
223
224 } catch (InvalidArgumentException $e) {
225 $this->showHelp();
226 $this->log('Error: ' . $e->getMessage(), 'red');
227 return 1;
228 }
229
230 $command = $positional[0];
231
232 $this->inputPath = $positional[1];
233 $this->outputPath = isset($positional[2]) ? $positional[2] : '-';
234
235 if ($this->outputPath !== '-') {
236 $this->stdout = fopen($this->outputPath, 'w');
237 }
238
239 if (!$this->inputFormat) {
240 if (substr($this->inputPath, -5) === '.json') {
241 $this->inputFormat = 'json';
242 } else {
243 $this->inputFormat = 'mimedir';
244 }
245 }
246 if (!$this->format) {
247 if (substr($this->outputPath, -5) === '.json') {
248 $this->format = 'json';
249 } else {
250 $this->format = 'mimedir';
251 }
252 }
253
254
255 $realCode = 0;
256
257 try {
258
259 while ($input = $this->readInput()) {
260
261 $returnCode = $this->$command($input);
262 if ($returnCode !== 0) $realCode = $returnCode;
263
264 }
265
266 } catch (EofException $e) {
267 // end of file
268 } catch (\Exception $e) {
269 $this->log('Error: ' . $e->getMessage(), 'red');
270 return 2;
271 }
272
273 return $realCode;
274
275 }
276
282 protected function showHelp() {
283
284 $this->log('Usage:', 'yellow');
285 $this->log(" vobject [options] command [arguments]");
286 $this->log('');
287 $this->log('Options:', 'yellow');
288 $this->log($this->colorize('green', ' -q ') . "Don't output anything.");
289 $this->log($this->colorize('green', ' -help -h ') . "Display this help message.");
290 $this->log($this->colorize('green', ' --format ') . "Convert to a specific format. Must be one of: vcard, vcard21,");
291 $this->log($this->colorize('green', ' --forgiving ') . "Makes the parser less strict.");
292 $this->log(" vcard30, vcard40, icalendar20, jcal, jcard, json, mimedir.");
293 $this->log($this->colorize('green', ' --inputformat ') . "If the input format cannot be guessed from the extension, it");
294 $this->log(" must be specified here.");
295 // Only PHP 5.4 and up
296 if (version_compare(PHP_VERSION, '5.4.0') >= 0) {
297 $this->log($this->colorize('green', ' --pretty ') . "json pretty-print.");
298 }
299 $this->log('');
300 $this->log('Commands:', 'yellow');
301 $this->log($this->colorize('green', ' validate') . ' source_file Validates a file for correctness.');
302 $this->log($this->colorize('green', ' repair') . ' source_file [output_file] Repairs a file.');
303 $this->log($this->colorize('green', ' convert') . ' source_file [output_file] Converts a file.');
304 $this->log($this->colorize('green', ' color') . ' source_file Colorize a file, useful for debbugging.');
305 $this->log(
306 <<<HELP
307
308If source_file is set as '-', STDIN will be used.
309If output_file is omitted, STDOUT will be used.
310All other output is sent to STDERR.
311
312HELP
313 );
314
315 $this->log('Examples:', 'yellow');
316 $this->log(' vobject convert contact.vcf contact.json');
317 $this->log(' vobject convert --format=vcard40 old.vcf new.vcf');
318 $this->log(' vobject convert --inputformat=json --format=mimedir - -');
319 $this->log(' vobject color calendar.ics');
320 $this->log('');
321 $this->log('https://github.com/fruux/sabre-vobject', 'purple');
322
323 }
324
332 protected function validate(Component $vObj) {
333
334 $returnCode = 0;
335
336 switch ($vObj->name) {
337 case 'VCALENDAR' :
338 $this->log("iCalendar: " . (string)$vObj->VERSION);
339 break;
340 case 'VCARD' :
341 $this->log("vCard: " . (string)$vObj->VERSION);
342 break;
343 }
344
345 $warnings = $vObj->validate();
346 if (!count($warnings)) {
347 $this->log(" No warnings!");
348 } else {
349
350 $levels = [
351 1 => 'REPAIRED',
352 2 => 'WARNING',
353 3 => 'ERROR',
354 ];
355 $returnCode = 2;
356 foreach ($warnings as $warn) {
357
358 $extra = '';
359 if ($warn['node'] instanceof Property) {
360 $extra = ' (property: "' . $warn['node']->name . '")';
361 }
362 $this->log(" [" . $levels[$warn['level']] . '] ' . $warn['message'] . $extra);
363
364 }
365
366 }
367
368 return $returnCode;
369
370 }
371
379 protected function repair(Component $vObj) {
380
381 $returnCode = 0;
382
383 switch ($vObj->name) {
384 case 'VCALENDAR' :
385 $this->log("iCalendar: " . (string)$vObj->VERSION);
386 break;
387 case 'VCARD' :
388 $this->log("vCard: " . (string)$vObj->VERSION);
389 break;
390 }
391
393 if (!count($warnings)) {
394 $this->log(" No warnings!");
395 } else {
396
397 $levels = [
398 1 => 'REPAIRED',
399 2 => 'WARNING',
400 3 => 'ERROR',
401 ];
402 $returnCode = 2;
403 foreach ($warnings as $warn) {
404
405 $extra = '';
406 if ($warn['node'] instanceof Property) {
407 $extra = ' (property: "' . $warn['node']->name . '")';
408 }
409 $this->log(" [" . $levels[$warn['level']] . '] ' . $warn['message'] . $extra);
410
411 }
412
413 }
414 fwrite($this->stdout, $vObj->serialize());
415
416 return $returnCode;
417
418 }
419
427 protected function convert($vObj) {
428
429 $json = false;
430 $convertVersion = null;
431 $forceInput = null;
432
433 switch ($this->format) {
434 case 'json' :
435 $json = true;
436 if ($vObj->name === 'VCARD') {
437 $convertVersion = Document::VCARD40;
438 }
439 break;
440 case 'jcard' :
441 $json = true;
442 $forceInput = 'VCARD';
443 $convertVersion = Document::VCARD40;
444 break;
445 case 'jcal' :
446 $json = true;
447 $forceInput = 'VCALENDAR';
448 break;
449 case 'mimedir' :
450 case 'icalendar' :
451 case 'icalendar20' :
452 case 'vcard' :
453 break;
454 case 'vcard21' :
455 $convertVersion = Document::VCARD21;
456 break;
457 case 'vcard30' :
458 $convertVersion = Document::VCARD30;
459 break;
460 case 'vcard40' :
461 $convertVersion = Document::VCARD40;
462 break;
463
464 }
465
466 if ($forceInput && $vObj->name !== $forceInput) {
467 throw new \Exception('You cannot convert a ' . strtolower($vObj->name) . ' to ' . $this->format);
468 }
469 if ($convertVersion) {
470 $vObj = $vObj->convert($convertVersion);
471 }
472 if ($json) {
473 $jsonOptions = 0;
474 if ($this->pretty) {
475 $jsonOptions = JSON_PRETTY_PRINT;
476 }
477 fwrite($this->stdout, json_encode($vObj->jsonSerialize(), $jsonOptions));
478 } else {
479 fwrite($this->stdout, $vObj->serialize());
480 }
481
482 return 0;
483
484 }
485
493 protected function color($vObj) {
494
495 fwrite($this->stdout, $this->serializeComponent($vObj));
496
497 }
498
506 protected function colorize($color, $str, $resetTo = 'default') {
507
508 $colors = [
509 'cyan' => '1;36',
510 'red' => '1;31',
511 'yellow' => '1;33',
512 'blue' => '0;34',
513 'green' => '0;32',
514 'default' => '0',
515 'purple' => '0;35',
516 ];
517 return "\033[" . $colors[$color] . 'm' . $str . "\033[" . $colors[$resetTo] . "m";
518
519 }
520
529 protected function cWrite($color, $str) {
530
531 fwrite($this->stdout, $this->colorize($color, $str));
532
533 }
534
535 protected function serializeComponent(Component $vObj) {
536
537 $this->cWrite('cyan', 'BEGIN');
538 $this->cWrite('red', ':');
539 $this->cWrite('yellow', $vObj->name . "\n");
540
556 $sortScore = function($key, $array) {
557
558 if ($array[$key] instanceof Component) {
559
560 // We want to encode VTIMEZONE first, this is a personal
561 // preference.
562 if ($array[$key]->name === 'VTIMEZONE') {
563 $score = 300000000;
564 return $score + $key;
565 } else {
566 $score = 400000000;
567 return $score + $key;
568 }
569 } else {
570 // Properties get encoded first
571 // VCARD version 4.0 wants the VERSION property to appear first
572 if ($array[$key] instanceof Property) {
573 if ($array[$key]->name === 'VERSION') {
574 $score = 100000000;
575 return $score + $key;
576 } else {
577 // All other properties
578 $score = 200000000;
579 return $score + $key;
580 }
581 }
582 }
583
584 };
585
586 $children = $vObj->children();
587 $tmp = $children;
588 uksort(
589 $children,
590 function($a, $b) use ($sortScore, $tmp) {
591
592 $sA = $sortScore($a, $tmp);
593 $sB = $sortScore($b, $tmp);
594
595 return $sA - $sB;
596
597 }
598 );
599
600 foreach ($children as $child) {
601 if ($child instanceof Component) {
602 $this->serializeComponent($child);
603 } else {
604 $this->serializeProperty($child);
605 }
606 }
607
608 $this->cWrite('cyan', 'END');
609 $this->cWrite('red', ':');
610 $this->cWrite('yellow', $vObj->name . "\n");
611
612 }
613
621 protected function serializeProperty(Property $property) {
622
623 if ($property->group) {
624 $this->cWrite('default', $property->group);
625 $this->cWrite('red', '.');
626 }
627
628 $this->cWrite('yellow', $property->name);
629
630 foreach ($property->parameters as $param) {
631
632 $this->cWrite('red', ';');
633 $this->cWrite('blue', $param->serialize());
634
635 }
636 $this->cWrite('red', ':');
637
638 if ($property instanceof Property\Binary) {
639
640 $this->cWrite('default', 'embedded binary stripped. (' . strlen($property->getValue()) . ' bytes)');
641
642 } else {
643
644 $parts = $property->getParts();
645 $first1 = true;
646 // Looping through property values
647 foreach ($parts as $part) {
648 if ($first1) {
649 $first1 = false;
650 } else {
651 $this->cWrite('red', $property->delimiter);
652 }
653 $first2 = true;
654 // Looping through property sub-values
655 foreach ((array)$part as $subPart) {
656 if ($first2) {
657 $first2 = false;
658 } else {
659 // The sub-value delimiter is always comma
660 $this->cWrite('red', ',');
661 }
662
663 $subPart = strtr(
664 $subPart,
665 [
666 '\\' => $this->colorize('purple', '\\\\', 'green'),
667 ';' => $this->colorize('purple', '\;', 'green'),
668 ',' => $this->colorize('purple', '\,', 'green'),
669 "\n" => $this->colorize('purple', "\\n\n\t", 'green'),
670 "\r" => "",
671 ]
672 );
673
674 $this->cWrite('green', $subPart);
675 }
676 }
677
678 }
679 $this->cWrite("default", "\n");
680
681 }
682
690 protected function parseArguments(array $argv) {
691
692 $positional = [];
693 $options = [];
694
695 for ($ii = 0; $ii < count($argv); $ii++) {
696
697 // Skipping the first argument.
698 if ($ii === 0) continue;
699
700 $v = $argv[$ii];
701
702 if (substr($v, 0, 2) === '--') {
703 // This is a long-form option.
704 $optionName = substr($v, 2);
705 $optionValue = true;
706 if (strpos($optionName, '=')) {
707 list($optionName, $optionValue) = explode('=', $optionName);
708 }
709 $options[$optionName] = $optionValue;
710 } elseif (substr($v, 0, 1) === '-' && strlen($v) > 1) {
711 // This is a short-form option.
712 foreach (str_split(substr($v, 1)) as $option) {
713 $options[$option] = true;
714 }
715
716 } else {
717
718 $positional[] = $v;
719
720 }
721
722 }
723
724 return [$options, $positional];
725
726 }
727
728 protected $parser;
729
735 protected function readInput() {
736
737 if (!$this->parser) {
738 if ($this->inputPath !== '-') {
739 $this->stdin = fopen($this->inputPath, 'r');
740 }
741
742 if ($this->inputFormat === 'mimedir') {
743 $this->parser = new Parser\MimeDir($this->stdin, ($this->forgiving ? Reader::OPTION_FORGIVING : 0));
744 } else {
745 $this->parser = new Parser\Json($this->stdin, ($this->forgiving ? Reader::OPTION_FORGIVING : 0));
746 }
747 }
748
749 return $this->parser->parse();
750
751 }
752
760 protected function log($msg, $color = 'default') {
761
762 if (!$this->quiet) {
763 if ($color !== 'default') {
764 $msg = $this->colorize($color, $msg);
765 }
766 fwrite($this->stderr, $msg . "\n");
767 }
768
769 }
770
771}
An exception for terminatinating execution or to throw for unit testing.
This is the CLI interface for sabre-vobject.
Definition: Cli.php:15
showHelp()
Shows the help message.
Definition: Cli.php:282
parseArguments(array $argv)
Parses the list of arguments.
Definition: Cli.php:690
serializeComponent(Component $vObj)
Definition: Cli.php:535
color($vObj)
Colorizes a file.
Definition: Cli.php:493
colorize($color, $str, $resetTo='default')
Returns an ansi color string for a color name.
Definition: Cli.php:506
cWrite($color, $str)
Writes out a string in specific color.
Definition: Cli.php:529
repair(Component $vObj)
Repairs a VObject file.
Definition: Cli.php:379
main(array $argv)
Main function.
Definition: Cli.php:99
log($msg, $color='default')
Sends a message to STDERR.
Definition: Cli.php:760
validate(Component $vObj)
Validates a VObject file.
Definition: Cli.php:332
convert($vObj)
Converts a vObject file to a new format.
Definition: Cli.php:427
serializeProperty(Property $property)
Colorizes a property.
Definition: Cli.php:621
readInput()
Reads the input file.
Definition: Cli.php:735
serialize()
Turns the object back into a serialized blob.
Definition: Component.php:284
children()
Returns a flat list of all the properties and components in this component.
Definition: Component.php:187
validate($options=0)
Validates the node for correctness.
Definition: Component.php:607
const VCARD30
vCard 3.0.
Definition: Document.php:44
const VCARD21
vCard 2.1.
Definition: Document.php:39
const VCARD40
vCard 4.0.
Definition: Document.php:49
Exception thrown by parser when the end of the stream has been reached, before this was expected.
const REPAIR
The following constants are used by the validate() method.
Definition: Node.php:27
Json Parser.
Definition: Json.php:19
BINARY property.
Definition: Binary.php:21
parameters()
Returns an iterable list of children.
Definition: Property.php:196
getParts()
Returns a multi-valued property.
Definition: Property.php:152
getValue()
Returns the current value.
Definition: Property.php:115
const OPTION_FORGIVING
If this option is passed to the reader, it will be less strict about the validity of the lines.
Definition: Reader.php:21
const VERSION
Full version number.
Definition: Version.php:17
$key
Definition: croninfo.php:18
foreach($paths as $path) if($argc< 3) $input
global $argv
Definition: svg-scanner.php:41