From fe02653016bed5eab485fc715646a3bcfad4384a Mon Sep 17 00:00:00 2001 From: DavidBadura Date: Thu, 5 Feb 2015 20:41:03 +0100 Subject: [PATCH] first draft --- .gitignore | 3 + .travis.yml | 26 ++++ README.md | 12 +- composer.json | 30 ++++ phpunit.xml.dist | 9 ++ src/Modify.php | 45 ++++++ src/Task.php | 57 +++++++ src/Taskwarrior.php | 290 +++++++++++++++++++++++++++++++++++ src/TaskwarriorException.php | 34 ++++ tests/TaskwarriorTest.php | 137 +++++++++++++++++ tests/bootstrap.php | 12 ++ 11 files changed, 654 insertions(+), 1 deletion(-) create mode 100644 .gitignore create mode 100644 .travis.yml create mode 100644 composer.json create mode 100644 phpunit.xml.dist create mode 100644 src/Modify.php create mode 100644 src/Task.php create mode 100644 src/Taskwarrior.php create mode 100644 src/TaskwarriorException.php create mode 100644 tests/TaskwarriorTest.php create mode 100644 tests/bootstrap.php diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9e270ad --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +composer.phar +composer.lock +vendor/ \ No newline at end of file diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..f46e3ac --- /dev/null +++ b/.travis.yml @@ -0,0 +1,26 @@ +language: php + +php: + - 5.4 + - 5.5 + - 5.6 + - hhvm + +env: + matrix: + - PREFER_LOWEST="--prefer-lowest" + - PREFER_LOWEST="" + +before_install: + - sudo add-apt-repository ppa:ultrafredde/ppa -y + - sudo apt-get update -qq + - sudo apt-get install -qq task + - task --version + +before_script: + - composer self-update + - composer update --prefer-source $PREFER_LOWEST + +matrix: + allow_failures: + - php: hhvm \ No newline at end of file diff --git a/README.md b/README.md index 0ca75b9..00efbe9 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,12 @@ # Taskwarrior -a php lib for taskwarrior + +```php +$tw = new \DavidBadura\Taskwarrior\Taskwarrior(); + +$task = new \DavidBadura\Taskwarrior\Task(); +$task->addTag('home'); + +$tw->save($task); + +$tasks = $tw->filter('+home'); +``` diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..05bbe35 --- /dev/null +++ b/composer.json @@ -0,0 +1,30 @@ +{ + "name": "davidbadura/taskwarrior", + "description": "a php lib for taskwarrior", + "keywords": [ + "task", + "taskwarrior" + ], + "type": "library", + "license": "MIT", + "authors": [ + { + "name": "David Badura", + "email": "d.badura@gmx.de" + } + ], + "require": { + "php": ">=5.4", + "symfony/process": "~2.3", + "jms/serializer": "0.16.*", + "symfony/filesystem": "~2.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "autoload": { + "psr-4": { + "DavidBadura\\Taskwarrior\\": "src" + } + } +} diff --git a/phpunit.xml.dist b/phpunit.xml.dist new file mode 100644 index 0000000..2a87093 --- /dev/null +++ b/phpunit.xml.dist @@ -0,0 +1,9 @@ + + + + + + ./tests + + + \ No newline at end of file diff --git a/src/Modify.php b/src/Modify.php new file mode 100644 index 0000000..714395f --- /dev/null +++ b/src/Modify.php @@ -0,0 +1,45 @@ + + */ +class Modify +{ + /** + * @var string + */ + private $description; + + /** + * @return string + */ + public function getDescription() + { + return $this->description; + } + + /** + * @param string $description + */ + public function setDescription($description) + { + $this->description = $description; + } + + /** + * @param Task $task + * @return Modify + */ + public static function createFromTask(Task $task) + { + $modify = new self(); + $modify->setDescription($task->getDescription()); + + return $modify; + } +} \ No newline at end of file diff --git a/src/Task.php b/src/Task.php new file mode 100644 index 0000000..a52079b --- /dev/null +++ b/src/Task.php @@ -0,0 +1,57 @@ + + */ +class Task +{ + /** + * @var string + * + * @JMS\Type(name="string") + */ + private $uuid; + + /** + * @var string + * + * @JMS\Type(name="string") + */ + private $description; + + /** + * @return string + */ + public function getUuid() + { + return $this->uuid; + } + + /** + * @param string $uuid + */ + public function setUuid($uuid) + { + $this->uuid = $uuid; + } + + /** + * @return string + */ + public function getDescription() + { + return $this->description; + } + + /** + * @param string $description + */ + public function setDescription($description) + { + $this->description = $description; + } +} \ No newline at end of file diff --git a/src/Taskwarrior.php b/src/Taskwarrior.php new file mode 100644 index 0000000..40e20f4 --- /dev/null +++ b/src/Taskwarrior.php @@ -0,0 +1,290 @@ + + */ +class Taskwarrior +{ + /** + * @var array + */ + private $rcOptions; + + /** + * @var Task[] + */ + private $tasks = []; + + /** + * @param string $taskrc + * @param string $taskData + * @param array $rcOptions + */ + public function __construct($taskrc = '~/.taskrc', $taskData = '~/.task', $rcOptions = []) + { + $this->rcOptions = array_merge( + array( + 'rc:' . $taskrc, + 'rc.data.location=' . $taskData, + 'rc.json.array=false', + 'rc.confirmation=no', + ), + $rcOptions + ); + } + + /** + * @param Task $task + */ + public function save(Task $task) + { + if (!$task->getUuid()) { + $this->add($task); + } else { + $this->edit($task); + } + } + + /** + * @param string $uuid + * @return Task + * @throws TaskwarriorException + */ + public function find($uuid) + { + if (isset($this->tasks[$uuid])) { + return $this->tasks[$uuid]; + } + + $tasks = $this->filter($uuid); + + if (count($tasks) == 0) { + return null; + } + + if (count($tasks) == 1) { + return $tasks[0]; + } + + throw new TaskwarriorException(); + } + + /** + * @param $filter + * @return Task[] + */ + public function filter($filter = '') + { + $result = $this->export($filter); + + foreach ($result as $key => $task) { + if (isset($this->tasks[$task->getUuid()])) { + + $result[$key] = $this->tasks[$task->getUuid()]; + + continue; + } + + $this->tasks[$task->getUuid()] = $task; + } + + return $result; + } + + /** + * @param Task $task + */ + public function delete(Task $task) + { + // todo + } + + /** + * @param Task $task + */ + public function done(Task $task) + { + // todo + } + + /** + * + */ + public function clear() + { + $this->tasks = []; + } + + /** + * @param $json + * @return string + * @throws TaskwarriorException + */ + private function import($json) + { + $fs = new Filesystem(); + + $file = tempnam(sys_get_temp_dir(), 'task') . '.json'; + $fs->dumpFile($file, $json); + + $output = $this->command('import', $file); + + if (!preg_match('/([0-9a-f]{8}\-[0-9a-f]{4}\-[0-9a-f]{4}\-[0-9a-f]{4}\-[0-9a-f]{12})/', $output, $matches)) { + throw new TaskwarriorException(); + } + + return $matches[1]; + } + + /** + * @param string $filter + * @return Task[] + */ + private function export($filter = '') + { + $tasks = array(); + + $json = $this->command('export', $filter); + + if (!$json) { + return $tasks; + } + + $jsons = explode("\n", $json); + + foreach ($jsons as $row) { + if (trim($row) == "") { + continue; + } + + $serializer = SerializerBuilder::create() + ->addDefaultHandlers() + ->build(); + + $tasks[] = $serializer->deserialize($row, 'DavidBadura\Taskwarrior\Task', 'json'); + } + + return $tasks; + } + + /** + * @param string $command + * @param string $filter + * @param array $options + * @return string + * @throws TaskwarriorException + */ + private function command($command, $filter = null, $options = array()) + { + $builder = $this->createProcessBuilder(); + + if ($filter) { + $builder->add($filter); + } + + $builder->add($command); + + foreach ($options as $param) { + $builder->add($param); + } + + $process = $builder->getProcess(); + $process->run(); + + if (!$process->isSuccessful()) { + throw new TaskwarriorException( + $process->getErrorOutput(), + $process->getExitCode(), + $process->getCommandLine() + ); + } + + return $process->getOutput(); + } + + /** + * @param Task $task + * @throws TaskwarriorException + */ + private function add(Task $task) + { + $json = $this->serializeTask($task); + $uuid = $this->import($json); + + $task->setUuid($uuid); + $this->tasks[$uuid] = $task; + + $this->update($task); + } + + /** + * @param Task $task + */ + private function edit(Task $task) + { + $modify = Modify::createFromTask($task); + $options = $this->modifyOptions($modify); + $this->command('modify', $task->getUuid(), $options); + } + + /** + * @param Task $task + */ + private function update(Task $task) + { + // todo + } + + /** + * @return ProcessBuilder + */ + private function createProcessBuilder() + { + $builder = new ProcessBuilder(); + + foreach ($this->rcOptions as $option) { + $builder->add($option); + } + + $builder->setPrefix('task'); + $builder->setTimeout(360); + + return $builder; + } + + /** + * + * @param Task $task + * @return string + */ + private function serializeTask(Task $task) + { + $serializer = SerializerBuilder::create() + ->addDefaultHandlers() + ->build(); + + $result = $serializer->serialize($task, 'json'); + + return str_replace("\\/", "/", $result); + } + + /** + * @param Modify $modify + * @return array + */ + private function modifyOptions(Modify $modify) + { + $array = []; + + $array[] = $modify->getDescription(); + + return $array; + } +} \ No newline at end of file diff --git a/src/TaskwarriorException.php b/src/TaskwarriorException.php new file mode 100644 index 0000000..157fd25 --- /dev/null +++ b/src/TaskwarriorException.php @@ -0,0 +1,34 @@ + + */ +class TaskwarriorException extends \Exception +{ + /** + * @var string + */ + private $command; + + /** + * @param string $message + * @param int $code + * @param string $command + */ + public function __construct($message = "", $code = 0, $command = '') + { + parent::__construct($message, $code, null); + } + + /** + * @return string + */ + public function getCommand() + { + return $this->command; + } +} \ No newline at end of file diff --git a/tests/TaskwarriorTest.php b/tests/TaskwarriorTest.php new file mode 100644 index 0000000..8d8625d --- /dev/null +++ b/tests/TaskwarriorTest.php @@ -0,0 +1,137 @@ + + */ +class TaskwarriorTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var Taskwarrior + */ + protected $taskwarrior; + + public function setUp() + { + $this->taskwarrior = new Taskwarrior(__DIR__ . '/.taskrc', __DIR__ . '/.task'); + } + + public function tearDown() + { + $fs = new Filesystem(); + $fs->remove(__DIR__ . '/.taskrc'); + $fs->remove(__DIR__ . '/.task'); + } + + public function testEmpty() + { + $tasks = $this->taskwarrior->filter(); + $this->assertEmpty($tasks); + } + + public function testSaveTask() + { + $task = new Task(); + $task->setDescription('foo'); + + $this->taskwarrior->save($task); + $this->taskwarrior->clear(); + + $this->assertNotEmpty($task->getUuid()); + + $result = $this->taskwarrior->find($task->getUuid()); + + $this->assertEquals($task, $result); + } + + public function testFindFromCache() + { + $task = new Task(); + $task->setDescription('foo'); + + $this->taskwarrior->save($task); + + $result = $this->taskwarrior->find($task->getUuid()); + + $this->assertSame($task, $result); + } + + public function testFilterFromCache() + { + $task = new Task(); + $task->setDescription('foo'); + + $this->taskwarrior->save($task); + + $result = $this->taskwarrior->filter($task->getUuid()); + + $this->assertSame($task, $result[0]); + } + + public function testDontFind() + { + $task = $this->taskwarrior->find('56464asd46s4adas54da6'); + $this->assertNull($task); + } + + public function testDoubleSave() + { + $task = new Task(); + $task->setDescription('foo'); + + $this->taskwarrior->save($task); + + $this->assertNotEmpty($task->getUuid()); + $uuid = $task->getUuid(); + + $this->taskwarrior->save($task); + + $this->assertEquals($uuid, $task->getUuid()); + $this->assertCount(1, $this->taskwarrior->filter()); + + $this->taskwarrior->clear(); + + $this->taskwarrior->save($task); + + $this->assertEquals($uuid, $task->getUuid()); + $this->assertCount(1, $this->taskwarrior->filter()); + } + + public function testFilterAll() + { + $task1 = new Task(); + $task1->setDescription('foo1'); + + $task2 = new Task(); + $task2->setDescription('foo2'); + + $this->taskwarrior->save($task1); + $this->taskwarrior->save($task2); + + $this->assertCount(2, $this->taskwarrior->filter()); + } + + public function testModifyDescription() + { + $task1 = new Task(); + $task1->setDescription('foo1'); + + $this->taskwarrior->save($task1); + + $this->assertEquals('foo1', $task1->getDescription()); + + $task1->setDescription('bar1'); + $this->taskwarrior->save($task1); + + $this->taskwarrior->clear(); + + $result = $this->taskwarrior->find($task1->getUuid()); + + $this->assertEquals('bar1', $result->getDescription()); + } +} \ No newline at end of file diff --git a/tests/bootstrap.php b/tests/bootstrap.php new file mode 100644 index 0000000..8370141 --- /dev/null +++ b/tests/bootstrap.php @@ -0,0 +1,12 @@ +