ILIAS  release_5-4 Revision v5.4.26-12-gabc799a52e6
Cli.php
Go to the documentation of this file.
1 <?php
2 
3 namespace Sabre\VObject;
4 
5 use
7 
15 class 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 
308 If source_file is set as '-', STDIN will be used.
309 If output_file is omitted, STDOUT will be used.
310 All other output is sent to STDERR.
311 
312 HELP
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 
392  $warnings = $vObj->validate(Node::REPAIR);
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 }
validate(Component $vObj)
Validates a VObject file.
Definition: Cli.php:332
main(array $argv)
Main function.
Definition: Cli.php:99
colorize($color, $str, $resetTo='default')
Returns an ansi color string for a color name.
Definition: Cli.php:506
global $argv
Definition: svg-scanner.php:41
readInput()
Reads the input file.
Definition: Cli.php:735
Json Parser.
Definition: Json.php:19
if($argc< 3) $input
const VERSION
Full version number.
Definition: Version.php:17
parseArguments(array $argv)
Parses the list of arguments.
Definition: Cli.php:690
cWrite($color, $str)
Writes out a string in specific color.
Definition: Cli.php:529
convert($vObj)
Converts a vObject file to a new format.
Definition: Cli.php:427
serializeComponent(Component $vObj)
Definition: Cli.php:535
log($msg, $color='default')
Sends a message to STDERR.
Definition: Cli.php:760
repair(Component $vObj)
Repairs a VObject file.
Definition: Cli.php:379
getParts()
Returns a multi-valued property.
Definition: Property.php:152
This is the CLI interface for sabre-vobject.
Definition: Cli.php:15
BINARY property.
Definition: Binary.php:21
serializeProperty(Property $property)
Colorizes a property.
Definition: Cli.php:621
MimeDir parser.
Definition: MimeDir.php:25
const VCARD30
vCard 3.0.
Definition: Document.php:44
serialize()
Turns the object back into a serialized blob.
Definition: Component.php:284
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
parameters()
Returns an iterable list of children.
Definition: Property.php:196
const REPAIR
The following constants are used by the validate() method.
Definition: Node.php:27
color($vObj)
Colorizes a file.
Definition: Cli.php:493
getValue()
Returns the current value.
Definition: Property.php:115
const VCARD21
vCard 2.1.
Definition: Document.php:39
const VCARD40
vCard 4.0.
Definition: Document.php:49
showHelp()
Shows the help message.
Definition: Cli.php:282
Exception thrown by parser when the end of the stream has been reached, before this was expected...
children()
Returns a flat list of all the properties and components in this component.
Definition: Component.php:187
$key
Definition: croninfo.php:18
validate($options=0)
Validates the node for correctness.
Definition: Component.php:607