123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761 |
- <?php
- namespace Sabre\VObject;
- use
- InvalidArgumentException;
- /**
- * This is the CLI interface for sabre-vobject.
- *
- * @copyright Copyright (C) 2007-2014 fruux GmbH. All rights reserved.
- * @author Evert Pot (http://evertpot.com/)
- * @license http://sabre.io/license/ Modified BSD License
- */
- class Cli {
- /**
- * No output
- *
- * @var bool
- */
- protected $quiet = false;
- /**
- * Help display
- *
- * @var bool
- */
- protected $showHelp = false;
- /**
- * Wether to spit out 'mimedir' or 'json' format.
- *
- * @var string
- */
- protected $format;
- /**
- * JSON pretty print
- *
- * @var bool
- */
- protected $pretty;
- /**
- * Source file
- *
- * @var string
- */
- protected $inputPath;
- /**
- * Destination file
- *
- * @var string
- */
- protected $outputPath;
- /**
- * output stream
- *
- * @var resource
- */
- protected $stdout;
- /**
- * stdin
- *
- * @var resource
- */
- protected $stdin;
- /**
- * stderr
- *
- * @var resource
- */
- protected $stderr;
- /**
- * Input format (one of json or mimedir)
- *
- * @var string
- */
- protected $inputFormat;
- /**
- * Makes the parser less strict.
- *
- * @var bool
- */
- protected $forgiving = false;
- /**
- * Main function
- *
- * @return int
- */
- public function main(array $argv) {
- // @codeCoverageIgnoreStart
- // We cannot easily test this, so we'll skip it. Pretty basic anyway.
- if (!$this->stderr) {
- $this->stderr = fopen('php://stderr', 'w');
- }
- if (!$this->stdout) {
- $this->stdout = fopen('php://stdout', 'w');
- }
- if (!$this->stdin) {
- $this->stdin = fopen('php://stdin', 'r');
- }
- // @codeCoverageIgnoreEnd
- try {
- list($options, $positional) = $this->parseArguments($argv);
- if (isset($options['q'])) {
- $this->quiet = true;
- }
- $this->log($this->colorize('green', "sabre/vobject ") . $this->colorize('yellow', Version::VERSION));
- foreach($options as $name=>$value) {
- switch($name) {
- case 'q' :
- // Already handled earlier.
- break;
- case 'h' :
- case 'help' :
- $this->showHelp();
- return 0;
- break;
- case 'format' :
- switch($value) {
- // jcard/jcal documents
- case 'jcard' :
- case 'jcal' :
- // specific document versions
- case 'vcard21' :
- case 'vcard30' :
- case 'vcard40' :
- case 'icalendar20' :
- // specific formats
- case 'json' :
- case 'mimedir' :
- // icalendar/vcad
- case 'icalendar' :
- case 'vcard' :
- $this->format = $value;
- break;
- default :
- throw new InvalidArgumentException('Unknown format: ' . $value);
- }
- break;
- case 'pretty' :
- if (version_compare(PHP_VERSION, '5.4.0') >= 0) {
- $this->pretty = true;
- }
- break;
- case 'forgiving' :
- $this->forgiving = true;
- break;
- case 'inputformat' :
- switch($value) {
- // json formats
- case 'jcard' :
- case 'jcal' :
- case 'json' :
- $this->inputFormat = 'json';
- break;
- // mimedir formats
- case 'mimedir' :
- case 'icalendar' :
- case 'vcard' :
- case 'vcard21' :
- case 'vcard30' :
- case 'vcard40' :
- case 'icalendar20' :
- $this->inputFormat = 'mimedir';
- break;
- default :
- throw new InvalidArgumentException('Unknown format: ' . $value);
- }
- break;
- default :
- throw new InvalidArgumentException('Unknown option: ' . $name);
- }
- }
- if (count($positional) === 0) {
- $this->showHelp();
- return 1;
- }
- if (count($positional) === 1) {
- throw new InvalidArgumentException('Inputfile is a required argument');
- }
- if (count($positional) > 3) {
- throw new InvalidArgumentException('Too many arguments');
- }
- if (!in_array($positional[0], array('validate','repair','convert','color'))) {
- throw new InvalidArgumentException('Uknown command: ' . $positional[0]);
- }
- } catch (InvalidArgumentException $e) {
- $this->showHelp();
- $this->log('Error: ' . $e->getMessage(), 'red');
- return 1;
- }
- $command = $positional[0];
- $this->inputPath = $positional[1];
- $this->outputPath = isset($positional[2])?$positional[2]:'-';
- if ($this->outputPath !== '-') {
- $this->stdout = fopen($this->outputPath, 'w');
- }
- if (!$this->inputFormat) {
- if (substr($this->inputPath, -5)==='.json') {
- $this->inputFormat = 'json';
- } else {
- $this->inputFormat = 'mimedir';
- }
- }
- if (!$this->format) {
- if (substr($this->outputPath,-5)==='.json') {
- $this->format = 'json';
- } else {
- $this->format = 'mimedir';
- }
- }
- $realCode = 0;
- try {
- while($input = $this->readInput()) {
- $returnCode = $this->$command($input);
- if ($returnCode!==0) $realCode = $returnCode;
- }
- } catch (EofException $e) {
- // end of file
- } catch (\Exception $e) {
- $this->log('Error: ' . $e->getMessage(),'red');
- return 2;
- }
- return $realCode;
- }
- /**
- * Shows the help message.
- *
- * @return void
- */
- protected function showHelp() {
- $this->log('Usage:', 'yellow');
- $this->log(" vobject [options] command [arguments]");
- $this->log('');
- $this->log('Options:', 'yellow');
- $this->log($this->colorize('green', ' -q ') . "Don't output anything.");
- $this->log($this->colorize('green', ' -help -h ') . "Display this help message.");
- $this->log($this->colorize('green', ' --format ') . "Convert to a specific format. Must be one of: vcard, vcard21,");
- $this->log($this->colorize('green', ' --forgiving ') . "Makes the parser less strict.");
- $this->log(" vcard30, vcard40, icalendar20, jcal, jcard, json, mimedir.");
- $this->log($this->colorize('green', ' --inputformat ') . "If the input format cannot be guessed from the extension, it");
- $this->log(" must be specified here.");
- // Only PHP 5.4 and up
- if (version_compare(PHP_VERSION, '5.4.0') >= 0) {
- $this->log($this->colorize('green', ' --pretty ') . "json pretty-print.");
- }
- $this->log('');
- $this->log('Commands:', 'yellow');
- $this->log($this->colorize('green', ' validate') . ' source_file Validates a file for correctness.');
- $this->log($this->colorize('green', ' repair') . ' source_file [output_file] Repairs a file.');
- $this->log($this->colorize('green', ' convert') . ' source_file [output_file] Converts a file.');
- $this->log($this->colorize('green', ' color') . ' source_file Colorize a file, useful for debbugging.');
- $this->log(
- <<<HELP
- If source_file is set as '-', STDIN will be used.
- If output_file is omitted, STDOUT will be used.
- All other output is sent to STDERR.
- HELP
- );
- $this->log('Examples:', 'yellow');
- $this->log(' vobject convert contact.vcf contact.json');
- $this->log(' vobject convert --format=vcard40 old.vcf new.vcf');
- $this->log(' vobject convert --inputformat=json --format=mimedir - -');
- $this->log(' vobject color calendar.ics');
- $this->log('');
- $this->log('https://github.com/fruux/sabre-vobject','purple');
- }
- /**
- * Validates a VObject file
- *
- * @param Component $vObj
- * @return int
- */
- protected function validate($vObj) {
- $returnCode = 0;
- switch($vObj->name) {
- case 'VCALENDAR' :
- $this->log("iCalendar: " . (string)$vObj->VERSION);
- break;
- case 'VCARD' :
- $this->log("vCard: " . (string)$vObj->VERSION);
- break;
- }
- $warnings = $vObj->validate();
- if (!count($warnings)) {
- $this->log(" No warnings!");
- } else {
- $levels = array(
- 1 => 'REPAIRED',
- 2 => 'WARNING',
- 3 => 'ERROR',
- );
- $returnCode = 2;
- foreach($warnings as $warn) {
- $extra = '';
- if ($warn['node'] instanceof Property) {
- $extra = ' (property: "' . $warn['node']->name . '")';
- }
- $this->log(" [" . $levels[$warn['level']] . '] ' . $warn['message'] . $extra);
- }
- }
- return $returnCode;
- }
- /**
- * Repairs a VObject file
- *
- * @param Component $vObj
- * @return int
- */
- protected function repair($vObj) {
- $returnCode = 0;
- switch($vObj->name) {
- case 'VCALENDAR' :
- $this->log("iCalendar: " . (string)$vObj->VERSION);
- break;
- case 'VCARD' :
- $this->log("vCard: " . (string)$vObj->VERSION);
- break;
- }
- $warnings = $vObj->validate(Node::REPAIR);
- if (!count($warnings)) {
- $this->log(" No warnings!");
- } else {
- $levels = array(
- 1 => 'REPAIRED',
- 2 => 'WARNING',
- 3 => 'ERROR',
- );
- $returnCode = 2;
- foreach($warnings as $warn) {
- $extra = '';
- if ($warn['node'] instanceof Property) {
- $extra = ' (property: "' . $warn['node']->name . '")';
- }
- $this->log(" [" . $levels[$warn['level']] . '] ' . $warn['message'] . $extra);
- }
- }
- fwrite($this->stdout, $vObj->serialize());
- return $returnCode;
- }
- /**
- * Converts a vObject file to a new format.
- *
- * @param Component $vObj
- * @return int
- */
- protected function convert($vObj) {
- $json = false;
- $convertVersion = null;
- $forceInput = null;
- switch($this->format) {
- case 'json' :
- $json = true;
- if ($vObj->name === 'VCARD') {
- $convertVersion = Document::VCARD40;
- }
- break;
- case 'jcard' :
- $json = true;
- $forceInput = 'VCARD';
- $convertVersion = Document::VCARD40;
- break;
- case 'jcal' :
- $json = true;
- $forceInput = 'VCALENDAR';
- break;
- case 'mimedir' :
- case 'icalendar' :
- case 'icalendar20' :
- case 'vcard' :
- break;
- case 'vcard21' :
- $convertVersion = Document::VCARD21;
- break;
- case 'vcard30' :
- $convertVersion = Document::VCARD30;
- break;
- case 'vcard40' :
- $convertVersion = Document::VCARD40;
- break;
- }
- if ($forceInput && $vObj->name !== $forceInput) {
- throw new \Exception('You cannot convert a ' . strtolower($vObj->name) . ' to ' . $this->format);
- }
- if ($convertVersion) {
- $vObj = $vObj->convert($convertVersion);
- }
- if ($json) {
- $jsonOptions = 0;
- if ($this->pretty) {
- $jsonOptions = JSON_PRETTY_PRINT;
- }
- fwrite($this->stdout, json_encode($vObj->jsonSerialize(), $jsonOptions));
- } else {
- fwrite($this->stdout, $vObj->serialize());
- }
- return 0;
- }
- /**
- * Colorizes a file
- *
- * @param Component $vObj
- * @return int
- */
- protected function color($vObj) {
- fwrite($this->stdout, $this->serializeComponent($vObj));
- }
- /**
- * Returns an ansi color string for a color name.
- *
- * @param string $color
- * @return string
- */
- protected function colorize($color, $str, $resetTo = 'default') {
- $colors = array(
- 'cyan' => '1;36',
- 'red' => '1;31',
- 'yellow' => '1;33',
- 'blue' => '0;34',
- 'green' => '0;32',
- 'default' => '0',
- 'purple' => '0;35',
- );
- return "\033[" . $colors[$color] . 'm' . $str . "\033[".$colors[$resetTo]."m";
- }
- /**
- * Writes out a string in specific color.
- *
- * @param string $color
- * @param string $str
- * @return void
- */
- protected function cWrite($color, $str) {
- fwrite($this->stdout, $this->colorize($color, $str));
- }
- protected function serializeComponent(Component $vObj) {
- $this->cWrite('cyan', 'BEGIN');
- $this->cWrite('red', ':');
- $this->cWrite('yellow', $vObj->name . "\n");
- /**
- * Gives a component a 'score' for sorting purposes.
- *
- * This is solely used by the childrenSort method.
- *
- * A higher score means the item will be lower in the list.
- * To avoid score collisions, each "score category" has a reasonable
- * space to accomodate elements. The $key is added to the $score to
- * preserve the original relative order of elements.
- *
- * @param int $key
- * @param array $array
- * @return int
- */
- $sortScore = function($key, $array) {
- if ($array[$key] instanceof Component) {
- // We want to encode VTIMEZONE first, this is a personal
- // preference.
- if ($array[$key]->name === 'VTIMEZONE') {
- $score=300000000;
- return $score+$key;
- } else {
- $score=400000000;
- return $score+$key;
- }
- } else {
- // Properties get encoded first
- // VCARD version 4.0 wants the VERSION property to appear first
- if ($array[$key] instanceof Property) {
- if ($array[$key]->name === 'VERSION') {
- $score=100000000;
- return $score+$key;
- } else {
- // All other properties
- $score=200000000;
- return $score+$key;
- }
- }
- }
- };
- $tmp = $vObj->children;
- uksort(
- $vObj->children,
- function($a, $b) use ($sortScore, $tmp) {
- $sA = $sortScore($a, $tmp);
- $sB = $sortScore($b, $tmp);
- return $sA - $sB;
- }
- );
- foreach($vObj->children as $child) {
- if ($child instanceof Component) {
- $this->serializeComponent($child);
- } else {
- $this->serializeProperty($child);
- }
- }
- $this->cWrite('cyan', 'END');
- $this->cWrite('red', ':');
- $this->cWrite('yellow', $vObj->name . "\n");
- }
- /**
- * Colorizes a property.
- *
- * @param Property $property
- * @return void
- */
- protected function serializeProperty(Property $property) {
- if ($property->group) {
- $this->cWrite('default', $property->group);
- $this->cWrite('red', '.');
- }
- $str = '';
- $this->cWrite('yellow', $property->name);
- foreach($property->parameters as $param) {
- $this->cWrite('red',';');
- $this->cWrite('blue', $param->serialize());
- }
- $this->cWrite('red',':');
- if ($property instanceof Property\Binary) {
- $this->cWrite('default', 'embedded binary stripped. (' . strlen($property->getValue()) . ' bytes)');
- } else {
- $parts = $property->getParts();
- $first1 = true;
- // Looping through property values
- foreach($parts as $part) {
- if ($first1) {
- $first1 = false;
- } else {
- $this->cWrite('red', $property->delimiter);
- }
- $first2 = true;
- // Looping through property sub-values
- foreach((array)$part as $subPart) {
- if ($first2) {
- $first2 = false;
- } else {
- // The sub-value delimiter is always comma
- $this->cWrite('red', ',');
- }
- $subPart = strtr(
- $subPart,
- array(
- '\\' => $this->colorize('purple', '\\\\', 'green'),
- ';' => $this->colorize('purple', '\;', 'green'),
- ',' => $this->colorize('purple', '\,', 'green'),
- "\n" => $this->colorize('purple', "\\n\n\t", 'green'),
- "\r" => "",
- )
- );
- $this->cWrite('green', $subPart);
- }
- }
- }
- $this->cWrite("default", "\n");
- }
- /**
- * Parses the list of arguments.
- *
- * @param array $argv
- * @return void
- */
- protected function parseArguments(array $argv) {
- $positional = array();
- $options = array();
- for($ii=0; $ii < count($argv); $ii++) {
- // Skipping the first argument.
- if ($ii===0) continue;
- $v = $argv[$ii];
- if (substr($v,0,2)==='--') {
- // This is a long-form option.
- $optionName = substr($v,2);
- $optionValue = true;
- if (strpos($optionName,'=')) {
- list($optionName, $optionValue) = explode('=', $optionName);
- }
- $options[$optionName] = $optionValue;
- } elseif (substr($v,0,1) === '-' && strlen($v)>1) {
- // This is a short-form option.
- foreach(str_split(substr($v,1)) as $option) {
- $options[$option] = true;
- }
- } else {
- $positional[] = $v;
- }
- }
- return array($options, $positional);
- }
- protected $parser;
- /**
- * Reads the input file
- *
- * @return Component
- */
- protected function readInput() {
- if (!$this->parser) {
- if ($this->inputPath!=='-') {
- $this->stdin = fopen($this->inputPath,'r');
- }
- if ($this->inputFormat === 'mimedir') {
- $this->parser = new Parser\MimeDir($this->stdin, ($this->forgiving?Reader::OPTION_FORGIVING:0));
- } else {
- $this->parser = new Parser\Json($this->stdin, ($this->forgiving?Reader::OPTION_FORGIVING:0));
- }
- }
- return $this->parser->parse();
- }
- /**
- * Sends a message to STDERR.
- *
- * @param string $msg
- * @return void
- */
- protected function log($msg, $color = 'default') {
- if (!$this->quiet) {
- if ($color!=='default') {
- $msg = $this->colorize($color, $msg);
- }
- fwrite($this->stderr, $msg . "\n");
- }
- }
- }
|