diff vendor/sabre/vobject/lib/Cli.php @ 7:430dbd5346f7

vendor sabre as distributed
author Charlie Root
date Sat, 13 Jan 2018 09:06:10 -0500
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vendor/sabre/vobject/lib/Cli.php	Sat Jan 13 09:06:10 2018 -0500
@@ -0,0 +1,761 @@
+<?php
+
+namespace Sabre\VObject;
+
+use
+    InvalidArgumentException;
+
+/**
+ * This is the CLI interface for sabre-vobject.
+ *
+ * @copyright Copyright (C) 2011-2015 fruux GmbH (https://fruux.com/).
+ * @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");
+        }
+
+    }
+
+}