示例#1
0
 public function testRecurr()
 {
     $tests = array(array('2014-01-01', '1d', '2014-01-02'), array('2014-01-01', '1w', '2014-01-08'), array('2014-01-01', '1m', '2014-02-01'), array('2014-01-01', '1y', '2015-01-01'), array('2014-01-01', '2d', '2014-01-03'), array('2014-01-01', '2w', '2014-01-15'), array('2014-01-01', '2m', '2014-03-01'), array('2014-01-01', '2y', '2016-01-01'), array('2014-01-31', '1w', '2014-02-07'), array('2014-01-31', '1m', '2014-03-03'), array('2014-01-01', '+1d', (new DateTime())->modify('+1day')->format('Y-m-d')), array('2014-01-01', '+1w', (new DateTime())->modify('+1week')->format('Y-m-d')), array('2014-01-01', '+1m', (new DateTime())->modify('+1month')->format('Y-m-d')), array('2014-01-01', '+1y', (new DateTime())->modify('+1year')->format('Y-m-d')), array('2014-01-03', '1b', '2014-01-06'), array('2014-01-03', '2b', '2014-01-06'), array('2014-01-03', '3b', '2014-01-06'), array('2014-01-03', '4b', '2014-01-07'), array('2014-01-04', '1wb', '2014-01-13'), array('2014-01-01', '1mb', '2014-02-03'), array('2014-01-03', '1yb', '2015-01-05'));
     foreach ($tests as $test) {
         $in = $test[0];
         $interval = $test[1];
         $out = $test[2];
         $ts = new DateTime($in);
         $r = new Recurrent($interval);
         $this->assertEquals($r->recurr($ts)->format('Y-m-d'), $out);
     }
 }
示例#2
0
文件: Gui.php 项目: nielsk/otodo
 public function start()
 {
     $this->readLine = new ReadLine();
     $this->readLine->setCompletitionCallback(function ($input) {
         return $this->readlineCompletion($input);
     });
     try {
         $this->readLine->historyLoad(Config::$config['gui']['history_file']);
     } catch (HistoryLoadException $hle) {
         echo $hle->getMessage() . PHP_EOL;
         exit(-1);
     }
     // Clear screen
     echo "c";
     while (true) {
         $this->loadIfChanged();
         $search = $this->search;
         if ($search === null) {
             $this->filteredTodos = $this->todos;
         } else {
             $this->filteredTodos = $this->todos->array_filter(function ($todo) use($search) {
                 foreach ($search as $s) {
                     if ($s['not'] && stripos($todo->text, $s['text']) !== false) {
                         return false;
                     }
                 }
                 foreach ($search as $s) {
                     if (!$s['not'] && stripos($todo->text, $s['text']) !== false) {
                         return true;
                     }
                 }
                 foreach ($search as $s) {
                     if (!$s['not']) {
                         return false;
                     }
                 }
                 return true;
             });
         }
         // Screen height
         $maxTodos = $this->getTerminalHeight() - 15;
         if ($maxTodos <= 0) {
             echo "c";
             echo 'Too small terminal, make it taller' . PHP_EOL;
             sleep(Config::$config['gui']['reload_timeout']);
             continue;
         }
         // Detect max length of every column
         $columns = Config::$config['gui']['columns'];
         $lengths = array();
         $minLengths = array();
         foreach ($columns as $column) {
             if (!isset($this->columns[$column])) {
                 echo 'Unknow column: ' . $column . ', check configuration gui.columns!' . PHP_EOL;
                 exit(-1);
             }
             $lengths[$column] = mb_strlen($this->columns[$column]['title']);
             $minLengths[$column] = $lengths[$column];
         }
         $pos = 0;
         foreach ($this->filteredTodos as $k => $todo) {
             $pos++;
             foreach ($columns as $column) {
                 $len = mb_strlen($this->columnValue($k, $column));
                 if ($len > $lengths[$column]) {
                     $lengths[$column] = $len;
                 }
             }
             if ($pos >= $maxTodos) {
                 break;
             }
         }
         // Screen width
         $maxWidth = $this->getTerminalWidth();
         $width = -1;
         foreach ($lengths as $length) {
             $width += $length + 3;
         }
         $needShorten = $width - $maxWidth;
         if ($needShorten > 0) {
             // Try to shorten few columns
             $posShortens = array();
             $posShortenSum = 0;
             foreach ($columns as $column) {
                 if ($this->columns[$column]['shorten'] && $lengths[$column] > $minLengths[$column]) {
                     $posShortens[$column] = $lengths[$column] - $minLengths[$column];
                     $posShortenSum += $posShortens[$column];
                 }
             }
             $shortened = 0;
             foreach ($posShortens as $column => $posShorten) {
                 $shorten = floor((double) $posShorten / (double) $posShortenSum * (double) $needShorten);
                 $lengths[$column] -= $shorten;
                 $posShortens[$column] -= $shorten;
                 $shortened += $shorten;
             }
             // Fix rounding problem
             foreach ($posShortens as $column => $posShorten) {
                 if ($shortened >= $needShorten) {
                     break;
                 }
                 if ($posShorten <= 0) {
                     continue;
                 }
                 $shorten = 1;
                 $lengths[$column] -= $shorten;
                 $posShortens[$column] -= $shorten;
                 $shortened += $shorten;
             }
             // Nothing more to shorten
             if ($shortened < $needShorten) {
                 echo "c";
                 echo 'Too small terminal, make it wider' . PHP_EOL;
                 sleep(Config::$config['gui']['reload_timeout']);
                 continue;
             }
         }
         // Got to the begging of screen
         echo "";
         // Show columns title
         $first = true;
         foreach ($columns as $column) {
             // Clear line
             echo "";
             if ($first) {
                 $first = false;
             } else {
                 echo '|';
             }
             echo $this->config2color(Config::$config['color']['title']);
             $this->printColumn($this->columns[$column]['title'], $lengths[$column], $this->columns[$column]['padTitle']);
             echo $this->config2color(Config::$config['color']['default']);
         }
         echo PHP_EOL;
         // Show todos
         $pos = 0;
         foreach ($this->filteredTodos as $k => $todo) {
             // Clear line
             echo "";
             if ($pos++ % 2 == 0) {
                 echo $this->config2color(Config::$config['color']['todo_odd']);
             } else {
                 echo $this->config2color(Config::$config['color']['todo_even']);
             }
             $now = new DateTime('today');
             if (!$todo->done && $todo->due !== null) {
                 $diff = $todo->due->diff($now);
                 if ($diff->days == 0) {
                     echo $this->config2color(Config::$config['color']['todo_due_today']);
                 } else {
                     if (!$diff->invert) {
                         echo $this->config2color(Config::$config['color']['todo_after_due']);
                     }
                 }
             }
             if ($todo->priority !== null) {
                 if (isset(Config::$config['color']['todo_prio_' . $todo->priority])) {
                     echo $this->config2color(Config::$config['color']['todo_prio_' . $todo->priority]);
                 } elseif (isset(Config::$config['color']['todo_prio'])) {
                     echo $this->config2color(Config::$config['color']['todo_prio']);
                 }
             }
             $first = true;
             foreach ($columns as $column) {
                 if ($first) {
                     $first = false;
                 } else {
                     echo '|';
                 }
                 $this->printColumn($this->columnValue($k, $column), $lengths[$column], $this->columns[$column]['padValue']);
             }
             echo $this->config2color(Config::$config['color']['default']);
             echo PHP_EOL;
             if ($pos >= $maxTodos) {
                 echo "..." . PHP_EOL;
                 break;
             }
         }
         // Clear rest of screen
         echo "";
         echo PHP_EOL;
         echo $this->message . PHP_EOL;
         echo 'c  Create             e  Edit' . PHP_EOL;
         echo 'r  Remove             a  Archive' . PHP_EOL;
         echo 'x  Mark as done       X  Unmark as done' . PHP_EOL;
         echo 'd  Set due date       D  Unset due date' . PHP_EOL;
         echo 'g  Set recurrent      G  Unset recurrent' . PHP_EOL;
         echo 'p  Priority           P  Unset priority' . PHP_EOL;
         echo 's  Sort: ';
         $first = true;
         foreach ($this->sort as $col => $asc) {
             if ($first) {
                 $first = false;
             } else {
                 echo ', ';
             }
             if (!$asc) {
                 echo '!';
             }
             echo $col;
         }
         echo PHP_EOL;
         echo '/  Search';
         if ($this->search !== null) {
             echo ': ';
             $first = true;
             foreach ($this->search as $s) {
                 if ($first) {
                     $first = false;
                 } else {
                     echo ', ';
                 }
                 if ($s['not']) {
                     echo '!';
                 }
                 echo $s['text'];
             }
         }
         echo PHP_EOL;
         echo 'q  Quit' . PHP_EOL;
         $cmd = $this->readLine->read('> ', '', Config::$config['gui']['reload_timeout']);
         if ($this->readLine->timeout) {
             continue;
         }
         $this->message = '';
         $this->readLine->historyAdd($cmd);
         $this->readLine->historySave(Config::$config['gui']['history_file']);
         if ($cmd === '') {
             continue;
         }
         switch ($cmd[0]) {
             // Create new task
             case 'c':
                 $text = trim(substr($cmd, 1));
                 if (empty($text)) {
                     $text = $this->readLine->read('Text: ');
                 }
                 if ($text === '') {
                     $this->error('Need text');
                 } else {
                     $t = new TodoEx($this->todos);
                     $t->text = $text;
                     $t->creationDate = new DateTime('today');
                     // Detect duplicity
                     $dup = $this->todos->searchSimilar($t);
                     if ($dup) {
                         echo 'Duplicity found: ' . $dup->text . PHP_EOL;
                         $confirm = $this->readLine->read('Really add (y/n)? ', 'y');
                         if ($confirm !== 'y') {
                             $this->error('Todo not added');
                             break;
                         }
                     }
                     $this->todos[] = $t;
                     $this->lastLineNumber = array_pop($this->todos->array_keys());
                     if ($this->changed()) {
                         $this->notice('Todo added');
                     }
                 }
                 break;
                 // Edit task
             // Edit task
             case 'e':
                 $num = $this->getLineNumber($cmd);
                 if ($num === null) {
                     break;
                 }
                 $text = $this->readLine->read('Text: ', $this->todos[$num]->text);
                 if ($text === '') {
                     $this->error('Need text');
                 } else {
                     $this->todos[$num]->text = $text;
                     if ($this->changed()) {
                         $this->notice('Todo ' . $num . ' changed');
                     }
                 }
                 break;
                 // Remove task
             // Remove task
             case 'r':
                 $num = $this->getLineNumber($cmd);
                 if ($num === null) {
                     break;
                 }
                 $confirm = $this->readLine->read('Do you really want to remove todo ' . $num . ' (y/n)? ', 'y');
                 if ($confirm === 'y') {
                     unset($this->todos[$num]);
                     if ($this->changed()) {
                         $this->notice('Todo ' . $num . ' removed');
                     }
                 } else {
                     $this->error('Todo ' . $num . ' NOT removed');
                 }
                 break;
                 // Archive
             // Archive
             case 'a':
                 $count = $this->todos->archive(Config::$config['core']['archive_file']);
                 if ($count) {
                     if ($this->changed()) {
                         $this->todos->sort($this->sort);
                         $this->notice($count . ' todo(s) archived');
                     }
                 } else {
                     $this->notice('No todos to archive');
                 }
                 break;
                 // Mark as done
             // Mark as done
             case 'x':
                 $num = $this->getLineNumber($cmd);
                 if ($num === null) {
                     break;
                 }
                 $this->todos[$num]->markDone();
                 if ($this->changed()) {
                     $this->notice('Todo ' . $num . ' marked done');
                 }
                 break;
                 // Unmark as done
             // Unmark as done
             case 'X':
                 $num = $this->getLineNumber($cmd);
                 if ($num === null) {
                     break;
                 }
                 $this->todos[$num]->unmarkDone();
                 if ($this->changed()) {
                     $this->notice('Todo ' . $num . ' unmarked done');
                 }
                 break;
                 // Set due date
             // Set due date
             case 'd':
                 $num = $this->getLineNumber($cmd);
                 if ($num === null) {
                     break;
                 }
                 $due = null;
                 if ($this->todos[$num]->due) {
                     $due = $this->todos[$num]->due->format(Config::$config['gui']['date_format_out']);
                 }
                 $str = $this->readLine->read('Due date: ', $due);
                 if ($str === '') {
                     $this->todos[$num]->due = null;
                     if ($this->changed()) {
                         $this->notice('Due date unset for todo ' . $num);
                     }
                 } else {
                     try {
                         $dt = $this->parseDate($str);
                         $this->todos[$num]->due = $dt;
                         if ($this->changed()) {
                             $this->notice('Due date set to ' . $dt->format('Y-m-d') . ' for todo ' . $num);
                         }
                     } catch (DateParseException $dpe) {
                         $this->error('Don\'t understand ' . $str);
                     }
                 }
                 break;
                 // Unset due date
             // Unset due date
             case 'D':
                 $num = $this->getLineNumber($cmd);
                 if ($num === null) {
                     break;
                 }
                 $this->todos[$num]->due = null;
                 if ($this->changed()) {
                     $this->notice('Due date unset for todo ' . $num);
                 }
                 break;
                 // Recurrent
             // Recurrent
             case 'g':
                 $num = $this->getLineNumber($cmd);
                 if ($num === null) {
                     break;
                 }
                 $recurrent = null;
                 if ($this->todos[$num]->recurrent) {
                     $recurrent = $this->todos[$num]->recurrent->toString();
                 }
                 $str = $this->readLine->read('Recurrent: ', $recurrent);
                 if ($str === '') {
                     $this->todos[$num]->recurrent = null;
                     if ($this->changed()) {
                         $this->notice('Todo ' . $num . ' set not recurrent');
                     }
                 } else {
                     try {
                         $r = new Recurrent($str);
                         $this->todos[$num]->recurrent = $r;
                         if ($this->changed()) {
                             $this->notice('Todo ' . $num . ' set recurrent ' . $r->toString());
                         }
                     } catch (RecurrentParseException $rpe) {
                         $this->error('Don\'t understand ' . $str);
                     }
                 }
                 break;
                 // Unset recurrent
             // Unset recurrent
             case 'G':
                 $num = $this->getLineNumber($cmd);
                 if ($num === null) {
                     break;
                 }
                 $this->todos[$num]->recurrent = null;
                 if ($this->changed()) {
                     $this->notice('Todo ' . $num . ' set not recurrent');
                 }
                 break;
                 // Sort
             // Sort
             case 's':
             case 'S':
                 $this->nextSort();
                 $this->notice('Sorting changed');
                 break;
                 // Search
             // Search
             case '/':
                 $search = trim(substr($cmd, 1));
                 if (empty($search)) {
                     $search = $this->readLine->read('Search: ');
                 }
                 if ($search === '') {
                     $this->search = null;
                 } else {
                     $search = explode(',', $search);
                     $this->search = array();
                     foreach ($search as $w) {
                         $not = false;
                         if ($w[0] === '!') {
                             $w = substr($w, 1);
                             $not = true;
                         }
                         $this->search[] = array('not' => $not, 'text' => trim($w));
                     }
                 }
                 break;
                 // Priority
             // Priority
             case 'p':
                 $num = $this->getLineNumber($cmd);
                 if ($num === null) {
                     break;
                 }
                 $str = $this->readLine->read('Priority: ', $this->todos[$num]->priority);
                 if ($str === '') {
                     $this->todos[$num]->priority = null;
                     if ($this->changed()) {
                         $this->notice('Priority unset for todo ' . $num);
                     }
                 } else {
                     if (preg_match('/^[a-zA-Z]$/', $str)) {
                         $prio = strtoupper($str);
                         $this->todos[$num]->priority = $prio;
                         if ($this->changed()) {
                             $this->notice('Priority set to ' . $prio . ' for todo ' . $num);
                         }
                     } else {
                         $this->error('Wrong priority ' . $str);
                     }
                 }
                 break;
                 // Unset priority
             // Unset priority
             case 'P':
                 $num = $this->getLineNumber($cmd);
                 if ($num === null) {
                     break;
                 }
                 $this->todos[$num]->priority = null;
                 if ($this->changed()) {
                     $this->notice('Priority unset for todo ' . $num);
                 }
                 break;
                 // Quit
             // Quit
             case 'q':
             case 'Q':
                 exit;
                 break;
         }
     }
 }