--- /dev/null
+#!/usr/bin/env php
+<?php
+
+class CSVReader {
+
+ private function __construct() { }
+
+ public static function readFile($file, $enclose = '"', $terminate = ',', $escape = '\\', $pascalEscapes = true, $charset = 'UTF-8') {
+ return self::readString(file_get_contents($file), $enclose, $terminate, $escape, $pascalEscapes, $charset);
+ }
+
+ public static function readString($string, $enclose = '"', $terminate = ',', $escape = '\\', $pascalEscapes = true, $charset = 'UTF-8') {
+ $inQuotes = false;
+ $inEscape = false;
+ $quoteInQuote = false;
+ $lastWasCR = false;
+
+ $len = strlen($string);
+ $value = '';
+ $line = array();
+ $result = new CSVReader();
+
+ if (empty($string)) {
+ return $result;
+ }
+
+ for ($i = 0; $i < $len; ++$i) {
+ $c = $string{$i};
+ if ($inEscape) {
+ $value .= $c;
+ $inEscape = false;
+ continue;
+ }
+ if ($c == $escape) {
+ $inEscape = true;
+ continue;
+ }
+ if ($quoteInQuote && $c != $enclose) {
+ $quoteInQuote = false;
+ $inQuotes = false;
+ }
+ if ($lastWasCR && $c != "\n") {
+ $lastWasCR = false;
+ $line[] = iconv($charset, 'UTF-8//TRANSLIT', $value);
+ $result->rows[] = $line;
+ $line = array();
+ $value = '';
+ }
+ if ($c == $enclose) {
+ if (!$inQuotes) {
+ $inQuotes = true;
+ } else if ($pascalEscapes) {
+ if ($quoteInQuote) {
+ $quoteInQuote = false;
+ $value .= $enclose;
+ } else {
+ $quoteInQuote = true;
+ }
+ } else {
+ $inQuotes = false;
+ }
+ continue;
+ }
+ if ($inQuotes) {
+ $value .= $c;
+ continue;
+ }
+ if ($c == $terminate) {
+ $line[] = iconv($charset, 'UTF-8//TRANSLIT', $value);
+ $value = '';
+ } else if ($c == "\r") {
+ $lastWasCR = true;
+ } else if ($c == "\n") {
+ $line[] = iconv($charset, 'UTF-8//TRANSLIT', $value);
+ $result->rows[] = $line;
+ $line = array();
+ $value = '';
+ $lastWasCR = false;
+ } else {
+ $value .= $c;
+ }
+ }
+ if (!empty($value)) {
+ $line[] = iconv($charset, 'UTF-8//TRANSLIT', $value);
+ }
+ if (!empty($line)) {
+ $result->rows[] = $line;
+ }
+ return $result;
+ }
+
+ public function free() {
+ $this->rows = array();
+ }
+
+ public function countFields() {
+ if ($this->rowValid()) {
+ return count($this->currentRow());
+ } else if (!empty($this->rows)) {
+ return count($this->rows[0]);
+ } else {
+ return 0;
+ }
+ }
+
+ public function countRows() {
+ return count($this->rows);
+ }
+
+ public function getFieldName($columnOffset) {
+ if (isset($this->names[$columnOffset])) {
+ return $this->names[$columnOffset];
+ } else {
+ throw OutOfBoundsException('cannot determine name for column at offset '.$columnOffset);
+ }
+ }
+
+ public function next($forSure = false) {
+ ++$this->rowCursor;
+ if ($this->rowValid()) {
+ return true;
+ } else if ($forSure) {
+ throw new OutOfBoundsException('there is no next row in this result set');
+ } else {
+ return false;
+ }
+ }
+
+ public function has($columnOffset) {
+ return $this->rowValid() && array_key_exists($columnOffset, $this->currentRow());
+ }
+
+ public function get($columnOffset) {
+ if (!isset($this->rows[$this->rowCursor])) {
+ throw new OutOfBoundsException('cannot get column '.$columnOffset.' of row '.$this->rowCursor.': invalid row');
+ }
+ if (!isset($this->rows[$this->rowCursor][$columnOffset])) {
+ throw new OutOfBoundsException('cannot get column '.$columnOffset.' of row '.$this->rowCursor.': invalid column');
+ }
+ return $this->rows[$this->rowCursor][$columnOffset];
+ }
+
+ public function loadColumnNamesFromFirstRow() {
+ if (isset($this->rows[0])) {
+ $this->setColumnNames($this->rows[0]);
+ } else {
+ throw new RuntimeException('there is no first row');
+ }
+ }
+
+ public function setColumnNames($names) {
+ $this->names = $names;
+ }
+
+ public function getNamed($name) {
+ if (in_array($name, $this->names)) {
+ return $this->get(array_search($name, $this->names));
+ } else {
+ throw new OutOfBoundsException($name.' is not a valid column name');
+ }
+ }
+
+ public function getNamedInt($name) {
+ return intval($this->getNamed($name));
+ }
+
+ public function getRowNumber() {
+ return $this->rowCursor + 1;
+ }
+
+ private function rowValid() {
+ return $this->rowCursor > -1 && $this->rowCursor < $this->countRows();
+ }
+
+ private function currentRow() {
+ return $this->rows[$this->rowCursor];
+ }
+
+ private $names = array();
+ private $rows = array();
+ private $rowCursor = -1;
+
+}
+
+function writeTicket(CSVReader $csv, $filename) {
+ $file = fopen($filename, 'w');
+ fputs($file, $csv->getNamed('Tracker').' #'.$csv->getnamed('#')
+ .' - '.$csv->getNamed('Subject').PHP_EOL.PHP_EOL);
+ fputs($file, ' '.str_pad(' Status: '.$csv->getNamed('Status'), 35)
+ .' Start date: '.$csv->getNamed('Start date').PHP_EOL);
+ fputs($file, ' '.str_pad('Priority: '.$csv->getNamed('Priority'), 35)
+ .' Due date: '.$csv->getNamed('Due date').PHP_EOL);
+ fputs($file, ' '.str_pad('Assignee: '.$csv->getNamed('Assignee'), 35)
+ .' Done: '.$csv->getNamed('% Done').'%'.PHP_EOL);
+ fputs($file, ' '.str_pad('Category: '.$csv->getNamed('Category'), 35)
+ .'Target version: '.$csv->getNamed('Target version').PHP_EOL.PHP_EOL);
+ $description = $csv->getNamed('Description');
+ $description = wordwrap(trim(str_replace(
+ array("\r\n", "\r", "\n"), array("\n", "\n", PHP_EOL), $description)));
+ fputs($file, $description.PHP_EOL);
+ fclose($file);
+}
+
+$dir = dirname(dirname(__FILE__)).'/issues';
+
+if (!is_dir($dir)) {
+ echo 'creating: ', $dir, PHP_EOL;
+ mkdir($dir);
+}
+
+$csv = CSVReader::readFile('http://luke.redirectme.net/redmine/projects/l2e/issues.csv?columns=all&description=1',
+ '"', ',', '\\', true, 'CP1252');
+$csv->loadColumnNamesFromFirstRow();
+$csv->next();
+
+$ids = array();
+
+while ($csv->next()) {
+ $ids[] = $csv->getNamed('#');
+ $filename = $csv->getNamed('#').' '.$csv->getNamed('Subject');
+ $filepath = $dir.'/'.$filename;
+ if (!file_exists($filepath)) {
+ echo 'writing: ', $filename, PHP_EOL;
+ writeTicket($csv, $filepath);
+ continue;
+ }
+ $modified = filemtime($filepath);
+ $updated = DateTime::createFromFormat(
+ 'm/d/Y h:i a', // 01/03/2013 05:56 am
+ $csv->getNamed('Updated'),
+ new DateTimeZone('Europe/Berlin'))->getTimestamp();
+ if ($updated > ($modified - 60)) {
+ echo 'updating: ', $filename, PHP_EOL;
+ writeTicket($csv, $filepath);
+ }
+}
+
+$existing = scandir($dir);
+
+foreach ($existing as $name) {
+ if (!is_file($dir.'/'.$name)) continue;
+ $id = intval($name);
+ if ($id > 0 && !in_array($id, $ids)) {
+ echo 'removing: ', $name, PHP_EOL;
+ unlink($dir.'/'.$name);
+ }
+}
+
+?>